Skip to main content

LinkRenderer

45 replies [Last post]
rbair
Offline
Joined: 2003-07-08

Hey Jeanette,

I'm trying to use the LinkRenderer on a JXTable with no success. I see in the examples that the "right" way to do this is to have a LinkModel.class returned for the column from the TableModel. However, I'm not able to modify the TableModel in this case (technically I could, but any solution that requires that much work is the wrong solution, IMO).

I want to do something like this:

<br />
  JXTable table = new JXTable();<br />
  //The model has two columns, customer_name<br />
  //(a String) and order_num (an Integer).<br />
  //I want both to have hyperlinks, and when they<br />
  //are clicked I want a different action listener<br />
  //invoked that will open the proper dialog or<br />
  //whatever<br />
  table.setModel(...);<br />
  TableColumn col = table.getColumnExt("customer_name");<br />
  col.setCellRenderer(new LinkRenderer(<br />
      new OpenCustomerListener()));<br />
  col = table.getColumnExt("order_num");<br />
  col.setCellRenderer(new LinkRenderer(<br />
      new OpenOrderListener()));<br />

This code doesn't appear to work. I tried to track down the cause, but the code was... hard to follow. LinkAction, JXHyperlink, LinkModel, BasicHyperlinkUI are all involved. The Hyperlinks doen't draw their underline (either always, or when hovered over). Initially the text didn't even show in the table -- I had to modifiy this line in LinkRenderer (in getTableCellRendererComponent):

<br />
    linkAction.setLink(value instanceof LinkModel ? (LinkModel) value : new LinkModel(value == null ? "" : value.toString()));<br />

(added in the auto LinkModel creation).

Richard

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
Patrick Wright

Hi Jeanette

> no problem, I just wanted to make sure we are on the same line. BTW,
> below is a quick flashing example - couldn't resist some fun coding .
> To run, you'll need the InteractiveTestCase of the swingx test package
> in the classpath (I'm too lazy to setup frames manually..)

Thanks, this is an interesting example. I now see where you were
headed with your earlier comments.

> Okay, so understood you correctly - but that's not a direction I would
> go without very good reason. It put's too much weight into the renderer
> itself for my taste. F.i. it makes it difficult to have the same effects
> across all cells of the table, independent of which concrete renderer is
> responsible for the content. Open to use-cases not or not nicely
> solvable with the current approach, of course

Well, hmm. I think the current approach makes sense because the
highlighters were promoted as a way to highlight the background of a
row; my interest is more in per-column highlighting (actually,
per-column, per-cell value). On the one hand, I like the current
approach in that the highlighter wraps whatever renderer is provided
and adjusts properties after the fact; but I like my idea in that one
is using a reusable renderer component and changing display properties
for that renderer. With the latter approach, we have a renderer which
knows how to change its display properties based on certain criteria,
and it delegates that task to highlighters. This is more in line with
how I've (perhaps incorrectly) approached the problem in the past.

I guess in a sense the weaknesses are the inversely related. If I use
a Highlighter that wraps a renderer, then I need to make sure that the
renderer knows it may be overridden by a Highlighter on the table.
Coversely, if I put the code in the renderer, I need to make sure I
can allow for common highlighting behavior across (possibly) multiple
renderers in cells in the same table.

> Hmm,... actually: no, I don't think so. Usually the font has to be as
> consistent as any other visual attribute, controlled by LF. Resetting
> style, size, whatever has to be as carefully adjusted relative to the
> un-highlighted case as colors.

I disagree, just because the font in apps I've been asked to develop
is usually dictated by the customer for the product, and is not
controlled by the LF, but rather by company style guides. Highlighting
(as a general term) is also dictated in that case by the company
guidelines. Highlighting of rows is a pretty well-defined visual
effect which GUI toolkits are supposed to be consistent about, but one
which users or companies may decide to override, for example, to
support a "ledger" style background highlighting. So I still think the
concerns are orthogonal.

> Interestingly, the example shows that (not really intended, came to a
> surprise to me :-) - the basic idea was to have a two-stage alert
> highlighting: negative values are colored, negative values below
> threshold are bolded and the row starts to flash. As highlighters are
> shareable across components the same pipeline is used in a table and a
> list view of the data. The "bolding" effect is as expected in winLF
> (which is my systemLF), but looks a bit weird in Metal, because now it's
> thinning a negative but not below threshold value (the list's default
> renderer has a bold font, different from the table). So developers still
> have to be very careful when applying visual effects.

I didn't see the bolding effect at all, just the font color and the
background flash.

> With experience, we'll get a better understanding what's really helpful
> - currently it's more on the playing side. I would love to see
> real-world examples which use highlighting creatively!

I would, too. The flashing background is something I've seen in
real-time data apps, but haven't had to work with it, really. Changing
font effects I've had to do but with data that is more static. Having
a general solution for it in the kit would be nice. This was really
easy to implement with some report tools, as I mentioned (more than
once, sorry!) using expressions attached to the column.

Cheers!
Patrick

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

Kleopatra

Hi Patrick,

> I guess in a sense the weaknesses are the inversely related. If I use
> a Highlighter that wraps a renderer, then I need to make sure that the
> renderer knows it may be overridden by a Highlighter on the table.
> Coversely, if I put the code in the renderer, I need to make sure I
> can allow for common highlighting behavior across (possibly) multiple
> renderers in cells in the same table.
>

yeah - and possibly across multiple tables or even different component
types.

>
>>Hmm,... actually: no, I don't think so. Usually the font has to be as
>>consistent as any other visual attribute, controlled by LF. Resetting
>>style, size, whatever has to be as carefully adjusted relative to the
>>un-highlighted case as colors.
>
>
> I disagree, just because the font in apps I've been asked to develop
> is usually dictated by the customer for the product, and is not
> controlled by the LF, but rather by company style guides.

had been sloppy - what I meant was more along the lines of "app
consistent visuals". Doesn't really matter if LF or company defined, I
think.

> Highlighting
> (as a general term) is also dictated in that case by the company
> guidelines. Highlighting of rows is a pretty well-defined visual
> effect which GUI toolkits are supposed to be consistent about, but one
> which users or companies may decide to override, for example, to
> support a "ledger" style background highlighting. So I still think the
> concerns are orthogonal.
>

Hmm, still don't see much of a difference - there are fallback rules and
contexts where developers may want to overrule them consistently. But
this is getting a bit academic anyway - we'll see what happens in
worldly use-cases :-)

>
> I didn't see the bolding effect at all, just the font color and the
> background flash.
>

hmm, weird ... the table.getFont() and table.getFont().derive(BOLD)
should be different enough in any case to make the effect recognizable.
Anyway it's just an example, you can change any visual aspect, add a
border, whatever.

>
> I would, too. The flashing background is something I've seen in
> real-time data apps, but haven't had to work with it, really. Changing
> font effects I've had to do but with data that is more static. Having
> a general solution for it in the kit would be nice. This was really
> easy to implement with some report tools, as I mentioned (more than
> once, sorry!) using expressions attached to the column.
>

all building blocks are there, we can construct a skyscraper out of them
given more concrete requirements :-) I like your idea to leverage Dave's
expression work here, sounds like they might play nicely with a
conditional highlighter.

Jeanette

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

Patrick Wright

Hi

> all building blocks are there, we can construct a skyscraper out of them
> given more concrete requirements :-) I like your idea to leverage Dave's
> expression work here, sounds like they might play nicely with a
> conditional highlighter.

I'm not sure if Dave's code is DataSet specific, e.g. if name binding
in expressions works only with DataSet and DataTable objects. I guess
that would be the first question, then see where it goes from there.

Thanks for the interactive and lively discussion!

Patrick

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

Kleopatra

Patrick Wright wrote:
>
> Thanks for the interactive and lively discussion!
>

My pleasure, thanks back!

Jeanette

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

rbair
Offline
Joined: 2003-07-08

Hey Jesse

> 4. It is not necessary to iterate over all the
> renderers in a table to determine which ones
> implement MouseListener. Instead, all you need to do
> is get the renderer for the current mouse position.

It depends on what you want to accomplish. Imagine I have a cell renderer that becomes more opaque the closer the mouse gets to it. In that case the renderer needs mouse information even when not being hovered over.

In any case, the cost of a traversal is very low. As long as the renderers are smart about the mouse movement and don't repaint themselves every time (unless they have to, as per the above example), I'd rather traverse. That does suggest, however, that we should have an abstract/default class from which to extend these "Live" renderers.

Richard

rbair
Offline
Joined: 2003-07-08

Jeanette,

> I don't have a very clear idea of your
> counter-proposal to both Part I and Part II (you've
> indicated several refactorings). Is there someplace I
> can look at what you are proposing, exactly?

Any progress on this? What is the state of affairs? Just looking for some closure :)

Kleopatra

Hi Richard,

>
> Any progress on this? What is the state of affairs? Just looking for some closure :)

Look at the code :-)

The Link* stuff is generalized as I outlined earlier. No delegate in the
base action layer - you convinced me . Still per-component-type
RolloverProducer/-Controller in cell-coordinates, and a tentative (read:
first draft) RolloverRenderer interface for "live" renderers.

Looking forward to the next round after my holidays.

Cheers
Jeanette

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

rbair
Offline
Joined: 2003-07-08

> > unless you can convince me that LinkModel actually
> y performs some
> function.
>
> Trying to - and I rarely give up

Incidently, my problem with LinkModel is not that it exists, but that it is/was being used as the model for the JXHyperlink via Action (and tied to LinkAction/renderer/etc)

[code]
private void setVisitedFromActionProperty(Action a) {
Boolean visited = (Boolean) a.getValue(LinkModel.VISITED_PROPERTY);
setVisited(visited != null ? visited.booleanValue() : false);
}
[/code]

Having a LinkModel is all fine and dandy, just as long as it isn't required (or even strongly encouraged, as in you don't have to go out of your way to avoid it) for dealing with JXHyperlink, LinkAction, LinkRenderer, etc.

Richard

Kleopatra

jdnc-interest@javadesktop.org wrote:

>
> [code]
> private void setVisitedFromActionProperty(Action a) {
> Boolean visited = (Boolean) a.getValue(LinkModel.VISITED_PROPERTY);
> setVisited(visited != null ? visited.booleanValue() : false);
> }
> [/code]
>

dooooohhh ... definitely a mistake, shit happens ;-)

Thanks
Jeanette

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

Kleopatra

Hi Richard,

New week, new round :-)

*PART 2 - LinkRenderer related*

As a general rule, renderers should not have any side-effects on the
component they are called to render on. Clients rely on that - renderers
can be freely created, configured, measured, thrown away without caring
if they might have any lasting effects. I would see auto-registration of
a listener - as you propose - as a nasty side-effect: it's never
unregistered. With the listener being a mouseMotionListener triggering
repaints with mouseMoved frequency, and one listener installed per
LinkRenderer ...

A specialization of that rule is that they must not change the
component's state in getXXRendererComponent. So no setting of the
table's cursor allowed there.

>> - LinkController: decide if the cell at the current
>> rollover coordinates require some special treatment,
>> decide which type of behaviour to trigger, and do it
>> if appropriate

> My problem with this is: how does the LinkController decide whether
the cell requires special treatment?

good question - will come back to it below. First I would like to
address a recurring argument:

> ... cell renderer already knows this information?

no it doesn't - a renderer is blind and deaf to its context until the
call to getXXRendererComponent. Even then it has only a very limited
snapshot vision on the component which is short-lived as well, ending
shortly after the method exited. And it's meant to be this way -
widening its viewport by adding logic is moving responsibility where it
doesn't belong, IMO.

> By putting this logic in the component, not only does it complicate
the implementation, but it also requires support in the component to work.

Again no - there is no logic on the component, the logic is in the
controller (which obviously is not yet factored in any respect ;-).
Where to install the controller is arguable - currently it's installed
by the component if enabled, resulting in at most one controller per
component.

Back to the question of how to decide about special treatment - that's
really an interesting point. A renderer is meant to be "controlled" from
the outside, but in this special case there is no nice way to do it. The
"special" behaviour is:

- appearance depends on mouseOver
- could trigger an action on mouseClicked, -released
- the action might have consquences on the appearance (and/or the value
shown)
- ??

This sounds like a mixture of renderer, editor and tooltip. So maybe we
have a third category of "cell component" (besides renderer and
editor) to support - it's probably not restricted to links, animating a
renderer on mouseover might look cool as well :-) Some kind of
LiveRenderer which does require controlling api somewhere - just as
tooltip and editor do.

Much to think about :-)

Cheers
Jeanette

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

Richard Bair

Hey Jeanette,

I still have arguments with the last one, I'll get to. But for this:

>As a general rule, renderers should not have any side-effects on the
>component they are called to render on. Clients rely on that - renderers
>can be freely created, configured, measured, thrown away without caring if
>they might have any lasting effects. I would see auto-registration of a
>listener - as you propose - as a nasty side-effect: it's never
>unregistered. With the listener being a mouseMotionListener triggering
>repaints with mouseMoved frequency, and one listener installed per
>LinkRenderer ...

Ya, its a hack. But I prefer it over the current design, both for ease of
use, simplicity, and lack of new API. Also, its per component, not per
LinkRenderer. Also, soft references are used allowing components to be
garbage collected. Also, adding/removing renderers a thousand times during
the life of a single component isn't a normal or natural use case. I don't
care about it. This all really doesn't matter though, I have proposed an
acceptable alternative at the end of this message.

Just for completeness, this approach isn't limited to LinkRenderer. What if
I want a cell renderer for a list that is made up in part by normal text,
images, and other components and in part by hyperlinks? You can think up
other instances where a cell renderer would want to know about the location
of the mouse cursor in a component.

But, I think there may be a way to satisfy my requirements at the bottom.

>A specialization of that rule is that they must not change the component's
>state in getXXRendererComponent. So no setting of the table's cursor
>allowed there.

I absolutely disagree. Setting the table cursor is well within the rights of
the cell renderer. I don't buy this argument. What's the difference between
the cell renderer setting the cursor on the table and the table asking the
cell renderer what the cursor should be?

> >> - LinkController: decide if the cell at the current
> >> rollover coordinates require some special treatment,
> >> decide which type of behaviour to trigger, and do it
> >> if appropriate
>
>
> > My problem with this is: how does the LinkController decide whether the
>cell requires special treatment?
>
>good question - will come back to it below. First I would like to address a
>recurring argument:
>
>Back to the question of how to decide about special treatment - that's
>really an interesting point. A renderer is meant to be "controlled" from
>the outside, but in this special case there is no nice way to do it. The
>"special" behaviour is:
>
>- appearance depends on mouseOver
>- could trigger an action on mouseClicked, -released
>- the action might have consquences on the appearance (and/or the value
>shown)
>- ??
>
>This sounds like a mixture of renderer, editor and tooltip. So maybe we
>have a third category of "cell component" (besides renderer and editor)
>to support - it's probably not restricted to links, animating a renderer on
>mouseover might look cool as well :-) Some kind of LiveRenderer which does
>require controlling api somewhere - just as tooltip and editor do.

This is really the crux of the whole argument. Its obvious that LinkRenderer
is a new type of renderer, one that changes its appearance and state
according to the state of the mouse. Given that, there is no clean way to
implement what we want and make it work with JTable/JList/JTree. That really
bothers me.

Here are my requirements:
* From a users point of view, using LinkRenderer should be _no different_
than any other renderer
* The API to support LinkRenderer must be extensible, so that any renderer
could get the same kind of "preferred" treatment
* Useable for JTree/JTable/JList

Also obvious is that having the mouse listeners in the renderer itself is
less than desireable (ya, its a hack). We actually could get around this
problem if:

* The renderer implements MouseMotionListener and/or MouseListener

I don't like the name "LinkController" (which is part of the confusion here
-- is it really just for hyperlinks? I hope not). So some of the proposal
below may overlap with what is already there -- that's fine.

Put a MouseAdapter on the JXTable/JXTree/JXList. All it does is, for each
mouse event, iterate over all the cell renderers on the component (only 1
for JXTree/JXList, one per column for JXTable). Pass the even on to the
renderer if it implements the right interface.

Now the LinkRenderer doesn't have to maintain the listener state itself, but
still benefits from it. Further, any new renderer that needs the same
behavior can get it easily enough.

Also, if the MouseAdapter is separate from the JXTable/JXTree/JXList then it
could just as easily be installed on a JTable/JTree/JList. Satisfying my
other requirement. By default every JXTree/JXTable/JXList would have it
installed and running -- I don't want to have to set "rolloverEnabled" to
true. Or, you could just have rolloverEnabled true by default if you are
concerned about even logic being fired when it is not necessary.

Then I can:
[code]
JXTable table = new JXTable(model);
table.getColumnModel().getColumn(2).setCellRenderer(linkRenderer);

MouseAdapter adapter = new [whatever we call it];
JTable table = new JTable(model);
table.addMouseMotionListener(adapter);
table.addMouseListener(adapter);
table.getColumnModel().getColumn(2).setCellRenderer(linkRenderer);
[/code]

I probably fumbled the code, but you get the idea. This to me is a
compromise because I have to specify the mouse motion listener and mouse
listener in the case of the JTable, but I can live with it.

Richard

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

Kleopatra

Hi Richard,

as usual a quick last word for the day, yeah I love it :-)

> Also, its per component, not per
> LinkRenderer.

really? Could have overlooked something, but to me it appears as if the
following will add two mouseMotionListeners:

[code]
table.getColumn(someindex).setCellRenderer(new LinkRenderer(someAction)));
table.getColumn(someOtherIndex.setCellRenderer(new
LinkRenderer(someOtherAction));
[/code]

> Also, soft references are used allowing components to be
> garbage collected. Also, adding/removing renderers a thousand times during
> the life of a single component isn't a normal or natural use case. I don't
> care about it.

I do care ;-) The use case is not overly uncommon: just imagine a table
with frequent structureChanged, auto-registration of per-column
renderers based on column class and auto-measuring the column width.
There quickly will be lots of unneeded but still listening mouseMotionL.

> Just for completeness, this approach isn't limited to LinkRenderer.

Neither is the current.

>
>>A specialization of that rule is that they must not change the component's
>>state in getXXRendererComponent. So no setting of the table's cursor
>>allowed there.
>
>
> I absolutely disagree. Setting the table cursor is well within the rights of
> the cell renderer. I don't buy this argument. What's the difference between
> the cell renderer setting the cursor on the table and the table asking the
> cell renderer what the cursor should be?
>

so lets agree to disagree - absolutely The first is set during a
paint cycle, the second outside of a paint cycle.

>
>
> This is really the crux of the whole argument. Its obvious that LinkRenderer
> is a new type of renderer, one that changes its appearance and state
> according to the state of the mouse. Given that, there is no clean way to
> implement what we want and make it work with JTable/JList/JTree. That really
> bothers me.
>
> Here are my requirements:
> * From a users point of view, using LinkRenderer should be _no different_
> than any other renderer

here we disagree: it's something else and there's a good probability
that developers need to be aware of it. Future will tell if so or how much.

> * The API to support LinkRenderer must be extensible, so that any renderer
> could get the same kind of "preferred" treatment

agreed. It is (*)

> * Useable for JTree/JTable/JList

not a problem, it is (*).

>
> Put a MouseAdapter on the JXTable/JXTree/JXList. All it does is, for each
> mouse event, iterate over all the cell renderers on the component (only 1
> for JXTree/JXList, one per column for JXTable). Pass the even on to the
> renderer if it implements the right interface.
>

naturally I don't like the idea to iterate - but make a LiveRenderer
implement a dedicated interface and let the controller use it if
appropriate is what I'm currently exploring. As the rollover/clicked
location is a property of the component, I wouldn't store it in the
renderer but in the component (as is done now ;-). As for names,
re-naming the LinkController to LinkRendererDelegate doesn't sound much
more intuitive to me .

>
> Also, if the MouseAdapter is separate from the JXTable/JXTree/JXList then it
> could just as easily be installed on a JTable/JTree/JList. Satisfying my
> other requirement. By default every JXTree/JXTable/JXList would have it
> installed and running -- I don't want to have to set "rolloverEnabled" to
> true. Or, you could just have rolloverEnabled true by default if you are
> concerned about even logic being fired when it is not necessary.
>

they are separate (*). Default to on or off is open to discussion - I
don't really care, only driven by old habit to consciously enable any
mouseMotion related acts, except when they are as highly optimized as
f.i. the ToolTipManager which is on by default.

(*) read: will be after refactoring. Repeating myself: they only live
inside the jx* class, there is nothing to prevent them from being
extracted into a stand-alone class and relax the requirement to control
a JX* to controlling a J*

Cheers (my dinner is calling)
Jeanette

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

rbair
Offline
Joined: 2003-07-08

> > Also, its per component, not per
> > LinkRenderer.
>
> really? Could have overlooked something, but to me it
> appears as if the
> following will add two mouseMotionListeners:

Shouldn't. The LinkRenderer should be adding mouse listeners in the getTableCellRenderer... method only if the table hasn't already had a listener added. At least, if that's not what its doing that's what it should be doing. But its moot anyway if we dump it.

> > Also, soft references are used allowing components
> to be
> > garbage collected. Also, adding/removing renderers
> a thousand times during
> > the life of a single component isn't a normal or
> natural use case. I don't
> > care about it.
>
> I do care ;-) The use case is not overly uncommon:
> just imagine a table
> with frequent structureChanged, auto-registration of
> per-column
> renderers based on column class and auto-measuring
> the column width.
> There quickly will be lots of unneeded but still
> listening mouseMotionL.

Darn, you got me :)

> > Just for completeness, this approach isn't limited
> to LinkRenderer.
>
> Neither is the current.

Perhaps its the name "LinkController" that causes me to think it is entended for use only with links. Anyway, that's good news.

> >>A specialization of that rule is that they must not
> change the component's
> >>state in getXXRendererComponent. So no setting of
> the table's cursor
> >>allowed there.
> >
> >
> > I absolutely disagree. Setting the table cursor is
> well within the rights of
> > the cell renderer. I don't buy this argument.
> What's the difference between
> > the cell renderer setting the cursor on the table
> and the table asking the
> > cell renderer what the cursor should be?
> >
>
> so lets agree to disagree - absolutely The first
> is set during a
> paint cycle, the second outside of a paint cycle.

Why does that make any difference?

> > Here are my requirements:
> > * From a users point of view, using LinkRenderer
> should be _no different_
> > than any other renderer
>
> here we disagree: it's something else and there's a
> good probability
> that developers need to be aware of it. Future will
> tell if so or how much.

I don't see any reason why they should be treated any differently. I have a strong aversion to making the developer using a LinkRenderer have to know anything about its implementation. I need more than "good probability" to justify more difficult API :-). Especially since making them aware of it can always happen in the future if necessary.

> > Put a MouseAdapter on the JXTable/JXTree/JXList.
> All it does is, for each
> > mouse event, iterate over all the cell renderers on
> the component (only 1
> > for JXTree/JXList, one per column for JXTable).
> Pass the even on to the
> > renderer if it implements the right interface.
> >
>
> naturally I don't like the idea to iterate - but make
> a LiveRenderer
> implement a dedicated interface and let the
> controller use it if
> appropriate is what I'm currently exploring. As the
> rollover/clicked
> location is a property of the component, I wouldn't
> store it in the
> renderer but in the component (as is done now ;-).

What's wrong with iterating? It's only for the case of the JTable anyway. Why create a new interface when MouseListener/MouseMotionListener already exists? I'm ambivilent about where rollover/clicked is stored. Clearly the LinkRenderer needs to be notified when a click occurs. I also prefer the LinkRenderer knowing where the mouse is, and was.

I find it so much clearer when I can look at LinkRenderer and it has all the state it needs to do its job. I hate it when debugging when I have to keep multiple classes state in mind to figure out what is happening.

Sure the component could have rollover/click state as well, I don't really care. In the case of LinkRenderer I might be able to extract the mouse location information from the component (in fact, I was telling Hans I think JComponent out to contain this kind of information). But the click information belongs on the LinkRenderer. That is, when a click occurs the LinkRenderer should be notified of it and do whatever it wants to.

> As
> for names,
> re-naming the LinkController to LinkRendererDelegate
> doesn't sound much
> more intuitive to me .

I would avoid the name "Link" altogether. Cell renderers could use the logic for much more than underlining a link. Using the name "Link" implies that the controller is used for Links only.

> > Also, if the MouseAdapter is separate from the
> JXTable/JXTree/JXList then it
> > could just as easily be installed on a
> JTable/JTree/JList. Satisfying my
> > other requirement. By default every
> JXTree/JXTable/JXList would have it
> > installed and running -- I don't want to have to
> set "rolloverEnabled" to
> > true. Or, you could just have rolloverEnabled true
> by default if you are
> > concerned about even logic being fired when it is
> not necessary.
> >
>
> they are separate (*). Default to on or off is open
> to discussion - I
> don't really care, only driven by old habit to
> consciously enable any
> mouseMotion related acts, except when they are as
> highly optimized as
> f.i. the ToolTipManager which is on by default.
>
> (*) read: will be after refactoring. Repeating
> myself: they only live
> inside the jx* class, there is nothing to prevent
> them from being
> extracted into a stand-alone class and relax the
> requirement to control
> a JX* to controlling a J*

Thats good.

Richard

Kleopatra

Hi Rich,

In fact we have two groups of arguments:

1) LinkModel/LinkAction related
2) LinkRenderer related

The basic problem (as I see it and what I think is what you stumbled
about when trying to use the LinkXX support) is that the LinkAction is
overly specialized to work nicely with a LinkModel. That's the
implementation of my vision of "Standard HL" in the core forum thread
groping for defining a "hyperlink spec".

http://forums.java.net/jive/thread.jspa?threadID=7880

As a consequence of the flexibility, usage is complicated (forgot a
reasonable simple default ) which in turn led to convenience support
on the JX* level - which concededly shouldn't be there, even if
encapsulated into a single method setVisitingDelegate(). As a
consequence of focusing on the "real hyperlink" the LinkRenderer has no
provision to support a "Generalized HL".

I fully agree with you that this limiting specialization must be lifted,
so for further debate, consider it done: a base "LinkAction" will not
have any reference to the LinkModel, consequently the LinkRenderer will
not be restricted to play with LinkModel and the convenience methods
will be removed.

Much intro, but now the story really starts

*PART 1) LinkModel/-Action related*

Our usual very central disagreement:

>>> > * LinkModel/LinkAction have overlapping duties
>
>>
>> no, they don't - or only if you disregard separation
>> of concerns between view and model

> Oh no, here we go again :-)

yeah, my pleasure to repeat it again and again until I find the right
wording to enter your brain and stick there

Trying again: the LinkModel is "a bean which represents a hyperlink". It
has (should have, or subclasses should have) all state to be shown up in
a reasonably nice way and connect to the represented document. That's
the thingy I would persist somewhere, f.i. in a bookmark list.

The details of how to do an actual "connect" and what to do with the
retrieved document, are up to clients to decide. They can vary wildly,
some examples:

- check if the connection is valid
- retrieve the document and show in the default browser
- retrieve the document and save locally
- retrieve the document and count how often "SwingLabs" is mentioned
- edit the bookmark
- ... (add infinitum)

This variation is supported by the visitingDelegate property of the
(current) LinkAction: custom delegates are pluggable. For simplicity
(hehe) an Action is re-used as delegate instead of requiring
yet-another-interface.

The concrete action of "connect" might have changed the LinkModel's
state in a similar variety

- validated might change the description
- successful retrieval might have toggled the "visited"
- edit properties directly
- ...

These changes must be reflected in the UI - and that's the other part of
LinkAction's responsibility: it listens to property changes and updates
its own the mapped visual properties (which in turn will trigger the
component visual update).

Trying to capture the design intention in pseudo-code (in lack of a
whiteboard!):

[code]

interface LinkVisitor {
void visit(T model);
}

class LinkAction... {

T model;
LinkVisitor visitor;

void setLink( visitor) {
clear();
this.visitor = visitor;
this.model = model;
mapModelProperties();
installListenerToModel();
}
}

void actionPerformed(...) {
visitor.visit(model);
}

void propertyChanged(..) {
mapModelProperty(propertyName, newPropertyValue);
}
}
[/code]

The current LinkAction is too limited in

a) T is restricted to type LinkModel
b) the mapping LinkModel properties --> Action visual attributes is
hardcoded (description --> name, urlRepresentation --> tooltip, visited
--> clicked - or something similar, don't nail me :)

When going more general, I would lift those limitations: T could be an
arbitrary type, the mapping of arbitrary object properties to action
visuals would be configurable. The trivial default wouldn't listen,
would use the toString as name and set the visited property directly.

With that in place, I can share the same LinkModel between different
LinkWidgets, showing different attributes and doing diffent things, f.i:

- a hyperlink, description as text, url as tooltip, triggering a connect
- a list of models, url as text, lastVisited as tooltip, sorted by url,
triggering an edit

[code] {

ListModel myBookmarks;
JXList list = new JXList(myBookmarks);
list.enableFilters();
LinkVisitor editVisitor = ....
LinkAction linkAction = new LinkAction(editVisitor);
linkAction.setPropertyMapping({ ACTION_NAME, "url",
SHORT_DESCRIPTION, "lastVisited"});
list.setCellRenderer(new LinkRenderer(linkAction));

LinkVisitor connectVisitor = ...
linkAction = new LinkAction(connectVisitor);
linkAction.setPropertyMapping({ ACTION_NAME, "description",
SHORT_DESCRIPTION, "url"});
JXHyperlink hyperlink = new JXHyperlink(linkAction);
// on list selection
linkAction.setModel(list.getSelectedItem());

}
[/code]

Now coming to your arguments:

> Thus, for all intents and purposes there is no use for LinkModel

there's at least the purpose I outlined above Naturally you can
achieve the same by subclassin, but I prefer a looser coupling with more
powerful default support of boring tasks.

> It [LinkModel] has some state that belongs in a URLLinkAction

I disagree - LinkModel is domain data and has state that can be mapped
to visual attributes of an (any) Action, but nothing that inherently
_is_ a visual action property.

>> The LinkModel is (meant as, concededly very
>> incomplete) the abstraction of a Hyperlink, like in
>> the html tag with a mixin of Bookmark, that's the
>> visited property. That model's "visited" is not the
>> equivalent to the button's clicked: the latter tries
>> to trigger a visit, but the visit might be
>> unsuccessful. It's up to the model to decide when it
>> would regard itself as "visited", most probably only
>> after a connection had been established.

>Why up to the model? Why not up to the Action? If the action is the
one performing the click event, then it is also the one that knows
whether it has been clicked.

yeah, it knows that it has been clicked (== the actionPerformed was
called), but it doesn't know if the model regards the visit(T) as
toggling the model visited property. In simple cases (if there is no
visitor, the model doesn't care, whatever) the action could substitute
its own clicked as visited. One drawback is that now you *really have to
share* the action - which takes over the role of model - even where
that's inappropriate, as f.i. in a tableModel.

> This is all needless complication

I disagree - it's neither complicated nor needless. What is complicated
is the current implementation which is not general enough.

> unless you can convince me that LinkModel actually performs some
function.

Trying to - and I rarely give up

I'll defer "PART 2 - LinkRenderer related" to a separate message, and
probably to one of the following days, my concentration is wabbling.

Cheers
Jeanette

---------------------------------------------------------------------
To unsubscribe, e-mail:
jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

Richard Bair

Hey Jeanette

>In fact we have two groups of arguments:
>
>1) LinkModel/LinkAction related
>2) LinkRenderer related

Yes, that's also how I see it.

>I fully agree with you that this limiting specialization must be lifted, so
>for further debate, consider it done: a base "LinkAction" will not have any
>reference to the LinkModel, consequently the LinkRenderer will not be
>restricted to play with LinkModel and the convenience methods will be
>removed.

Which convenience methods? The ones in the JX* components, or the renderer?
Could you elaborate a bit? Thanks.

> > Oh no, here we go again :-)
>
>yeah, my pleasure to repeat it again and again until I find the right
>wording to enter your brain and stick there

Sometimes it takes a club to get it in there :)

>Trying again: the LinkModel is "a bean which represents a hyperlink". It
>has (should have, or subclasses should have) all state to be shown up in a
>reasonably nice way and connect to the represented document. That's the
>thingy I would persist somewhere, f.i. in a bookmark list.
[...]
>With that in place, I can share the same LinkModel between different
>LinkWidgets, showing different attributes and doing diffent things, f.i:
>
>- a hyperlink, description as text, url as tooltip, triggering a connect
>- a list of models, url as text, lastVisited as tooltip, sorted by url,
>triggering an edit
[...]
>yeah, it knows that it has been clicked (== the actionPerformed was
>called), but it doesn't know if the model regards the visit(T) as toggling
>the model visited property. In simple cases (if there is no visitor, the
>model doesn't care, whatever) the action could substitute its own clicked
>as visited. One drawback is that now you *really have to share* the action
>- which takes over the role of model - even where that's inappropriate, as
>f.i. in a tableModel.

That makes sense. I'll concede that having a LinkModel is useful. However,
as I see it there are two logical subdivisions with hyperlinks:
* Those that act as buttons
* Those that model/represent a real URL

>[code]
>
>interface LinkVisitor {
> void visit(T model);
>}
>
>class LinkAction... {
>
> T model;
> LinkVisitor visitor;
>
> void setLink( visitor) {
> clear();
> this.visitor = visitor;
> this.model = model;
> mapModelProperties();
> installListenerToModel();
> }
>}
>
> void actionPerformed(...) {
> visitor.visit(model);
> }
>
> void propertyChanged(..) {
> mapModelProperty(propertyName, newPropertyValue);
> }
>}
>[/code]

Gross for the first case (where it is basically just a button), cool for the
second case (where it is a URL). Why don't we push the delegate off to
URLLinkAction instead of putting it in LinkAction? Lets leave action as
bare-bones as possible. So, it would be something more like:

[code]
public class LinkAction extends AbstractActionExt {
//adds Visited property
}
public class URLLinkAction extends LinkAction {
//deals with tracking changes in link model and reflecting them, etc
}
[/code]

>Now coming to your arguments:
>
> > Thus, for all intents and purposes there is no use for LinkModel
>
>there's at least the purpose I outlined above Naturally you can achieve
>the same by subclassin, but I prefer a looser coupling with more powerful
>default support of boring tasks.

I'm not sure what this refers to exactly. I will say that:

[code]
link.setAction(new LinkAction() {
public void actionPerformed(ActionEvent ae) {
//blah
});
}
[/code]

is much better than

[code]
Action a = new AbstractAction() {
public void actionPerformed(ActionEvent ae) {
//blah
};
link.setAction(new LinkAction(a));
[/code]

and not because it saves a line of code :). It is conceptually much easier
to understand and follow without the extra level of indirection. The fewer
moving pieces, the better.

> > This is all needless complication
>
>I disagree - it's neither complicated nor needless. What is complicated is
>the current implementation which is not general enough.

It *is* needless complication for the simple (aka common) case. Having a
delegate for the LinkAction is too complicated if all I want to do is
implement an ActionPerformed and be done with it.

>I'll defer "PART 2 - LinkRenderer related" to a separate message, and
>probably to one of the following days, my concentration is wabbling.

Lets work this one out, and then we'll get to LinkRenderer.

Richard

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

Kleopatra

Richard Bair wrote:
>
>
> That makes sense. I'll concede that having a LinkModel is useful. However,
> as I see it there are two logical subdivisions with hyperlinks:
> * Those that act as buttons
> * Those that model/represent a real URL
>

From the perspective of a JXHyperlink that's not important - it's
always just a button which configures itself from an arbitrary action.
The only difference (besides having a special UIDelegate) is that it
does have two foreground colors which can be controlled by a boolean
value with key "clicked" in the action. It's on the same level as any of
the other action visual keys (like name, image whatever). Strictly
speaking, we don't need a LinkAction - all we need is a place to store
the key, could be done f.i. in AbstractActionExt.

A stripped down LinkAction as you propose is too lightweight for my
taste, it doesn't add anything useful. IMO, it should be prepared to do
some work - developers can use the default or subclass and override
performed, just as they like - they are free to decide.

>
> Gross for the first case (where it is basically just a button), cool for the
> second case (where it is a URL). Why don't we push the delegate off to
> URLLinkAction instead of putting it in LinkAction? Lets leave action as
> bare-bones as possible. So, it would be something more like:
>

no, even the first case can be backed by a model, that's nothing limited
to an URL.

> I'm not sure what this refers to exactly. I will say that:
>
> [code]
> link.setAction(new LinkAction() {
> public void actionPerformed(ActionEvent ae) {
> //blah
> });
> }
> [/code]
>
> is much better than
>
> [code]
> Action a = new AbstractAction() {
> public void actionPerformed(ActionEvent ae) {
> //blah
> };
> link.setAction(new LinkAction(a));
> [/code]
>
> and not because it saves a line of code :). It is conceptually much easier
> to understand and follow without the extra level of indirection. The fewer
> moving pieces, the better.
>
>

"better" is relative, depends on what you want to achieve: the feature
makes your day if you need the extra level. If you don't need it -
simply go the first. There's nothing precluding the subclassing approach.

My current point of view is that we either get rid off LinkAction
altogether or let it do something substantial. In the latter case the
question is what "substantial" means

So back to use-cases - I would like to see a rough sketch how you would
solve a case I outlined in my previous message (use an arbitrary model,
f.i. a customer if you don't like the LinkModel :-), assuming that the
connectAction and editAction are working on the model in a fairly
complicated way, so they are predefined to be re-usable.

Cheers and a happy weekend
Jeanette

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

rbair
Offline
Joined: 2003-07-08

Hi Jeanette

> Strictly
> speaking, we don't need a LinkAction - all we need is
> a place to store
> the key, could be done f.i. in AbstractActionExt.

Of course. The reason for the LinkAction is to have typed access to that key, which is critical if the LinkAction is to play nice with GUI builders (must follow the javabeans pattern).

> > Gross for the first case (where it is basically
> just a button), cool for the
> > second case (where it is a URL). Why don't we push
> the delegate off to
> > URLLinkAction instead of putting it in LinkAction?
> Lets leave action as
> > bare-bones as possible. So, it would be something
> more like:
> >
>
> no, even the first case can be backed by a model,
> that's nothing limited
> to an URL.

But forcing a delegating pattern? A visitor? Yuck! I've been using hyperlinks for over 3 years (since when I wrote the first JHyperlink), and I've never had such a need. All I really wanted was a link looking thing (at the time a label, subsequently a button) that fires an action performed method.

LinkAction exists to simplify the use of an Action with the JXHyperlink, or for a LinkRenderer. It has typed access to the "visited" property. It of course has the actionPerformed method. Thats it. Lets not complicate the basic use case!

> "better" is relative, depends on what you want to
> achieve: the feature
> makes your day if you need the extra level. If you
> don't need it -
> simply go the first. There's nothing precluding the
> subclassing approach.

I'm really confused here. You proposed a LinkAction with a LinkVisitor and model. Subclass and ignore them? Why not subclass and add them. That's much better.

When I start using a new component, I look at the API. I see what is required. Then I create what is required in order to use the component. In the case of your proposed LinkAction, I as a user would first see the constructor, create a LinkVisitor, create some kind of model, pass the two in to the constructor, and curse your name forever, not realizing I could pass null or subclass :)

That's the way app developers I've worked with approach using an API. In my experience they don't want to delve into the details and see how it works -- they just want to wire it up and make it work.

If the basic API requires visitors and models, then its too hard. Its just too complicated.

> My current point of view is that we either get rid
> off LinkAction
> altogether or let it do something substantial. In the
> latter case the
> question is what "substantial" means

It does -- it adds typed access to the visitor property. That really is _all_ it should be doing. If there really is a use case for using delegates and models (which I still really doubt, except maybe with the LinkModel, though I'm still dubious), it doesn't belong in LinkAction. Create a DelegatingLinkAction or put it in URLLinkAction or something. Then you have your use case if you need it. I see the old 80/20 rule here: the vast majority of the time people will just want the basic actionPerformed.

> So back to use-cases - I would like to see a rough
> sketch how you would
> solve a case I outlined in my previous message (use
> an arbitrary model,
> f.i. a customer if you don't like the LinkModel :-),
> assuming that the
> connectAction and editAction are working on the model
> in a fairly
> complicated way, so they are predefined to be
> re-usable.

It depends on the situation, of course. Here's one way:
[code]
public class MyFrame extends JFrame {
private Customer cust;

private void initGui() {
//...
}

private final class ShowCustomerAction extends LinkAction {
super("View " + cust.getName() + "'s details");
}

public void actionPerformed(ActionEvent ae) {
JFrame frm = new CustomerFrame(cust);
frm.setVisible(true);
}
}
}
[/code]

Or some such thing. Different if it is an action used in a LinkRenderer, perhaps. In this case the text of the action is based on the model, which is something I don't see the LinkAction/LinkVisitor/model approach handling gracefully. In fact, it appears you'd have to subclass anyway.

Of course if the action was meant to be shared among multiple windows I'd have the constructor take the Customer object rather than just using what is in the frame. In either case, there isn't a Visitor, thank goodness :)

Cheers
Richard

Kleopatra

Hi Richard,

>
>
>>Strictly
>>speaking, we don't need a LinkAction - all we need is
>>a place to store
>>the key, could be done f.i. in AbstractActionExt.
>
>
> Of course. The reason for the LinkAction is to have typed access to that key, which is critical if the LinkAction is to play nice with GUI builders (must follow the javabeans pattern).
>
>

never liked builders except my own - but point taken. Still, the
linkAction as you suggested is too lightweight, and useless in renderers
if the action needs to be targeted on the current value of the
table/list cell.

Maybe we had a misunderstanding about the LinkVisitor - the outline was
to make you understand what I was after, not the real thing. Which might
or might not have a delegate for doing the thing, we'll see :-) My
current local version doesn't - because the implementation using a
actionListener/visitor is too restrictive, subclasses might decide to
delegate by binding to a "model" method via EventProxy

>
> Or some such thing. Different if it is an action used in a LinkRenderer, perhaps.
>

different if used in a renderer, indeed

> In this case the text of the action is based on the model, which is
something I don't see the LinkAction/LinkVisitor/model approach handling
gracefully. In fact, it appears you'd have to subclass anyway.

maybe you should look again - that's exactly what the (overspecialized,
we agree on this) current LinkAction supports out-of-the-box.

Jeanette

BTW: hey guy! calm down ... this isn't politics

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

rbair
Offline
Joined: 2003-07-08

Hi Jeanette,

> > Of course. The reason for the LinkAction is to have
> typed access to that key, which is critical if the
> LinkAction is to play nice with GUI builders (must
> follow the javabeans pattern).
> >
> never liked builders except my own - but point taken.
> Still, the
> linkAction as you suggested is too lightweight, and
> useless in renderers
> if the action needs to be targeted on the current
> value of the
> table/list cell.

I see now why you did what you did. It was so that LinkRenderer could tell LinkAction what it was operating on for actionPerformed.

One of the problems we appear to be having is a cross-up of what does what. My original understanding (partially or totally based on names) was that:

* LinkAction was the Action for JXHyperlinks, also used in LinkRenderer
* LinkModel was the model for JXHyperlinks
* LinkController was for dealing with LinkRenderers

All of which I profoundly disagree with (JXHyperlink doesn't need a LinkModel, LinkAction shouldn't have a delegate, visitor, model or anything else by default since JXHyperlink is just a glorified button, embedding renderer specific code ala LinkRenderer into JX* is bad).

In reality it appears from our conversation that:

* LinkAction was really designed for the LinkRenderer use case
* LinkModel is not a Swing model, but rather just keeps a url and other hyperlink like information
* LinkController is useable for things other than LinkRenderer

I maintain that LinkAction, by itself, should do nothing more than add a visited property. LinkModel is reasonable, but might need a better name since the "model" part made me, at least, think it was a Swing model like TableModel, ListModel, TreeModel, etc. LinkController should have a new name that doesn't imply it is for Links only, but for any renderer that needs mouse event notification to do its job.

A LinkAction subclass that has the concept of a "model" for use with LinkRenderer sounds like a reasonable idea. Again, I'd make it a subclass so as not to overly complicate the simple case.

> Maybe we had a misunderstanding about the LinkVisitor
> - the outline was
> to make you understand what I was after, not the real
> thing. Which might
> or might not have a delegate for doing the thing,
> we'll see :-) My
> current local version doesn't - because the
> implementation using a
> actionListener/visitor is too restrictive, subclasses
> might decide to
> delegate by binding to a "model" method via
> EventProxy

Yes, I definitely misunderstood. Sorry!

> > In this case the text of the action is based on
> n the model, which is
> something I don't see the
> LinkAction/LinkVisitor/model approach handling
> gracefully. In fact, it appears you'd have to
> subclass anyway.
>
>
> maybe you should look again - that's exactly what the
> (overspecialized,
> we agree on this) current LinkAction supports
> out-of-the-box.

But a generic version could not, clearly? You were arguing for a generic version, right? The generic version could not hande something like displaying a different icon depending on the state of the customer (return customer, new customer, customer with bad credit, etc), correct?

I don't have a very clear idea of your counter-proposal to both Part I and Part II (you've indicated several refactorings). Is there someplace I can look at what you are proposing, exactly?

> BTW: hey guy! calm down ... this isn't politics

:) Don't worry, I'm not upset. I guess I've been at Sun to long. We tend to get pretty animated about API discussions, even though there isn't any malice or anything involved!

These discussions are always so much harder in print than in person, too. It may take us 3 weeks to get where we'd be in 30 minutes if we were in the same room.

Richard

jessewilson
Offline
Joined: 2003-06-14

I really like the API you have designed for the "Table Mouse Over Support" class, but I have some suggestions...

1. I'd name the MouseAdapter subclass to "RendererMouseOverSupport", since it provides mouse over abilities to JTable and other rendered components.

2. I'd change the API of the RendererMouseOverSupport class so that instead of calling its constructor then adding it as a mouse listener and a mouse motion listener, the class would have a static 'install()' method that returned an instance:
[code]
public class RendererMouseOverSupport {
public static RendererMouseOverSupport install(JTable table);
public static RendererMouseOverSupport install(JList list);
public static RendererMouseOverSupport install(JTree tree);
public void dispose();
}
[/code]
. . . and then the user can call the 'dispose()' method to uninstall the support class later. This simplifies the API somewhat.

3. I'd probably support a plain-old JTable. I don't think it's necessary to require the full-blown JXTable here, since the support class simply has to call into the public API of JTable to do its repainting etc.

4. It is not necessary to iterate over all the renderers in a table to determine which ones implement MouseListener. Instead, all you need to do is get the renderer for the current mouse position.

I have a bit of experience on this one 'cause I've already implemented something very similar. What my class did was install the cell editor when the mouse rolled over, and uninstall it when it rolled out. This worked to overcome many of JTable's weaknesses:
- Normal JTable check box cell editors are really painful to interact with, since sometimes clicks are lost when entering 'edit' mode
- Hyperlinks can rollover with the hand cursor without much special code since they're not merely rendered components anymore
Our class was configurable column-by-column, so some cells edited on rollover (like hyperlinks) while others didn't (like text fields). The other nice thing about using editors was that it was trivial to put a bunch of links on a JPanel in some custom layout, and they would rollover independently without any custom code to set that up.

Cheers,
Jesse
http://publicobject.com/glazedlists/

Kleopatra

Hi Jesse,

didn't forget your suggestions - only was distracted by generics ;-)

As for names - they certainly have to be revised. Same holds for
convenience methods. Only, first I would like to get the abstractions
halfway right, f.i. LinkController is very similar for J/X/List and
-Table (not yet done for -Tree) - so my next step will be to extract.
Playing with generics again... sigh.

Yes I agree, there is not much specical in a XComponent compared to a
core ancestor, so the base support will be for core as well.

>
> I have a bit of experience on this one 'cause I've already implemented something very similar. What my class did was install the cell editor when the mouse rolled over, and uninstall it when it rolled out. This worked to overcome many of JTable's weaknesses:

interesting approach - though I'm a bit weary about mis-using the
editing mechanism to have the renderer component in the component
hierarchy. After all, it's not about changing the table's value. And
what happens if we start editing at a lead cell (by any keystroke or F2)
which is different from the mouseover cell? Or if the renderer must be
used for rendering a cell "somewhere else" - repaints might happen at
any time.

In the long run it might turn out that really adding a dedicated
"RolloverRenderer" to the table with a newly to be established mechanism
is the only clean way to solve. For now, there's a RolloverRenderer
interface which renderers with rollover effects should implement. Makes
the Controller's life easier - but certainly that's not the end of the
story. As at many SwingLabs places: work-in-progress ;)

Thanks for your feedback
Jeanette

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

jessewilson
Offline
Joined: 2003-06-14

> interesting approach - though I'm a bit weary about
> mis-using the editing mechanism....

Yeah, it was definitely somewhat of a hack, but I still think it was the best way for us to go at that time.

One of JTable's weaknesses is that it's not possible to have interactive components in cells without renderers. For an exercise in frustration, try to emulate the Safari downloads window using a JTable! It has rollover buttons and progress bars, and these are things that JTable's editor/renderer approach doesn't handle well.

Cheers,
Jesse

Patrick Wright

> One of JTable's weaknesses is that it's not possible to have interactive components in cells without renderers. For an exercise in frustration, try to emulate the Safari downloads window using a JTable! It has rollover buttons and progress bars, and these are things that JTable's editor/renderer approach doesn't handle well.

Just lurking here: an idea of had before is that there are a number of
(possibly orthogonal) concerns a "renderer" has to satisfy. For
example, sometimes I've wanted to control the font or colors used in
rendering a cell, which may depend on the content but of course is
orthogonal to it. Then there is the form in which the content is
rendered itself. Anyway, in some UI environments, I remember Crystal
Reports, you can make the font color, size, weight, etc. dependent on
some expression (pulled from the row or cell content). The background
(rollover), borders, etc. of a cell are/may be similarly orthogonal.
Haven't really worked this out in my mind, but I've found myself
re-creating general renderer functionality across several projects
(usually where I didn't have the license to take code from one project
to another).

As an example, my model may return a double value meant to represent a
currency, and in the renderer I want to format that as a currency
value (as text string), and, if it meets certain criteria, highlight
or color it. And this has to interact nicely with other concerns, such
that I may want background highlighting when the cell is selected. Or
I may want the background to flash when certain criteria is met, like
in a stock or pricing application, where it might flash if the
currency is lower or higher than some bounds.

How exactly to design for that, I'm not sure; never found a solution
that made me happy, but never worked on it for months at a time,
either.

Just a note!
Patrick

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

Kleopatra

Hi Patrick, Jesse,

>>One of JTable's weaknesses is that it's not possible to have interactive components in cells without renderers. For an exercise in frustration, try to emulate the Safari downloads window using a JTable! It has rollover buttons and progress bars, and these are things that JTable's editor/renderer approach doesn't handle well.
>

yeah, have been through the loops

>
> Just lurking here: an idea of had before is that there are a number of
> (possibly orthogonal) concerns a "renderer" has to satisfy. For
> example, sometimes I've wanted to control the font or colors used in
> rendering a cell, which may depend on the content but of course is
> orthogonal to it. Then there is the form in which the content is
> rendered itself. Anyway, in some UI environments, I remember Crystal
> Reports, you can make the font color, size, weight, etc. dependent on
> some expression (pulled from the row or cell content). The background
> (rollover), borders, etc. of a cell are/may be similarly orthogonal.

I think we made a major jump in SwingX with the introduction of
Highlighters: they are orthogonal to the "normal" rendering concern of
showing the content. A powerful subclass is the ConditionalHighlighter:
with it we can solve many of the content-derived visual change
requirements. Their limitation is the same as renderers' - they are
meant to be passiv, controlled "from the outside", breaking into life
only at the very moment of applying the highlight. F.i. a
RolloverHighlighter is a ConditionalHighlighter which colors the row on
mouseOver which takes effect on a repaint triggered by a RolloverController.

> Or
> I may want the background to flash when certain criteria is met, like
> in a stock or pricing application, where it might flash if the
> currency is lower or higher than some bounds.
>

.. is not directly supported. You need an additional adapter layer,
listening to the model, checking conditions and starting a timer which
toggles a Highlighter's background (f.i.) periodically.

Supporting a live progressbar as a renderer is one step further into
model business, IMO: there we have a real value change and should go
through model changes.

On the whole, we have a broad spectrum of rendering enhancement:

- static per row/column/cell "highlight" of every visual property
(though only colors are implemented by default)
- the same conditionally based on content
- easy to add adapter layer to real-time following content changes (this
is not really new, only now we have an additional hook - configure the
highlighter instead of decorating the model or overstuff the renderer
itself)

Plus, a new (not yet full-blown) concept of an "LiveRenderer" which can
react to mouseover and/or keystrokes which in turn might lead to the
necessity of actually adding it which in turn might require some deeper
changes ... thoughts for SwingX 3

Interesting thread, keep it coming!

Jeanette

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

Patrick Wright

Hi Jeanette

> I think we made a major jump in SwingX with the introduction of
> Highlighters: they are orthogonal to the "normal" rendering concern of
> showing the content. A powerful subclass is the ConditionalHighlighter:
> with it we can solve many of the content-derived visual change
> requirements. Their limitation is the same as renderers' - they are
> meant to be passiv, controlled "from the outside", breaking into life
> only at the very moment of applying the highlight. F.i. a
> RolloverHighlighter is a ConditionalHighlighter which colors the row on
> mouseOver which takes effect on a repaint triggered by a RolloverController.

Excellent review of the current situation. I agree that the
"highlighter" is a great test case for how we can (gradually) extract
reusable renderer functionality into some set of components, adapters,
etc.

> .. is not directly supported. You need an additional adapter layer,
> listening to the model, checking conditions and starting a timer which
> toggles a Highlighter's background (f.i.) periodically.
>
> Supporting a live progressbar as a renderer is one step further into
> model business, IMO: there we have a real value change and should go
> through model changes.

Yes, this may be partly a binding issue, then partly an issue of
getting the renderer to look good, by default, out of the box. I guess
there is a secondary notification (already in place in highlighter)
when the model change causes a change, indirectly, in the
renderer--each renderer (or the master renderer) needs to notify the
table that that cell was changed.

>
> On the whole, we have a broad spectrum of rendering enhancement:
>
> - static per row/column/cell "highlight" of every visual property
> (though only colors are implemented by default)
> - the same conditionally based on content
> - easy to add adapter layer to real-time following content changes (this
> is not really new, only now we have an additional hook - configure the
> highlighter instead of decorating the model or overstuff the renderer
> itself)

In an ideal world, a couple of other things:
- additional simple renderer components, as with highlighter--a
progress bar (maybe a couple different ones) and a font renderer
- a simple timer adapter
- some sort of expression support to make setting this up easier; this
is pretty common in reporting tools (which is what I am basing my
ideas on). we could probably leverage Dave's work on DataSet
expressions, since that would take care of most cases, people could
drop down to Java if necessary for more complex cases.

An interesting problem is how these things interact. If we take a
simple case, there is a background highlighter and a font highlighter.
The BG highlighter is set to alternate row, the font highlighter
attached to a certain column in the model, setting a cell's font color
to red when the value is negative. It seems to me we have a natural
chain of responsibility. The renderer has a chain of highlighters
attached to it, and when renderer is requested (because the table is
updating the cell), the renderer sets default display options (BG,
font, color, etc.) then asks its chain of renderers to do their thing.
This would not be efficient but relatively easy to code, as you just
loop over your highlighters and ask them to modify the JComponent that
is rendering the cell.

Hmm.
Patrick
>
> Plus, a new (not yet full-blown) concept of an "LiveRenderer" which can
> react to mouseover and/or keystrokes which in turn might lead to the
> necessity of actually adding it which in turn might require some deeper
> changes ... thoughts for SwingX 3
>
>
> Interesting thread, keep it coming!
>
> Jeanette
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
> For additional commands, e-mail: jdnc-help@jdnc.dev.java.net
>
>

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

Kleopatra

Hi Patrick,

>
> Yes, this may be partly a binding issue, then partly an issue of
> getting the renderer to look good, by default, out of the box. I guess
> there is a secondary notification (already in place in highlighter)
> when the model change causes a change, indirectly, in the
> renderer--each renderer (or the master renderer) needs to notify the
> table that that cell was changed.
>

just to clarify: there's no coupling from between model and highlighter
(the highlighter is shareable and doesn't care about who applies it to
which renderer). But there is change notification about highlighter
state changes, so in a sequence:

[code]
// adding a highlighter makes the table listen
// to highlighter state changes
table.addHighlighter(someHighlighter);
someHighlighter.setSomeColor(...);
[/code]

will trigger the table to repaint itself.

>
>>On the whole, we have a broad spectrum of rendering enhancement:
>>
>>- static per row/column/cell "highlight" of every visual property
>>(though only colors are implemented by default)
>>- the same conditionally based on content
>>- easy to add adapter layer to real-time following content changes (this
>>is not really new, only now we have an additional hook - configure the
>>highlighter instead of decorating the model or overstuff the renderer
>>itself)
>
>
> In an ideal world, a couple of other things:
> - additional simple renderer components, as with highlighter--a
> progress bar (maybe a couple different ones) and a font renderer
> - a simple timer adapter
> - some sort of expression support to make setting this up easier; this
> is pretty common in reporting tools (which is what I am basing my
> ideas on). we could probably leverage Dave's work on DataSet
> expressions, since that would take care of most cases, people could
> drop down to Java if necessary for more complex cases.
>

how did I know you would come up with a considerably longer wishlist
. Great!

> An interesting problem is how these things interact. If we take a
> simple case, there is a background highlighter and a font highlighter.
> The BG highlighter is set to alternate row, the font highlighter
> attached to a certain column in the model, setting a cell's font color
> to red when the value is negative. It seems to me we have a natural
> chain of responsibility. The renderer has a chain of highlighters
> attached to it, and when renderer is requested (because the table is
> updating the cell), the renderer sets default display options (BG,
> font, color, etc.) then asks its chain of renderers to do their thing.
> This would not be efficient but relatively easy to code, as you just
> loop over your highlighters and ask them to modify the JComponent that
> is rendering the cell.
>

Currently the chain of responsibility is in the HighlighterPipeline and
externally invoked by the component (or whoever is doing a
prepareRenderer or equivalent). Your use-case would be solved by adding
an AlternateH and a ConditionalHighlighter which checks the cell value
for negative number before changing the foreground. Hmm, do you suggest
to literally move it into the renderer itself? Not sure if I would like
that ...

Anyway, there are issues with the "chain" - it turned out to be a bit
difficult (read: I couldn't find a way to do it ;-) to make sure the
chained changed attributes end up looking nice in the end. Several
highlighters effecting background may make the text totally unreadable, f.i.

Cheers (getting hungry :-)
Jeanette

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

Patrick Wright

Hi Jeanette

> just to clarify: there's no coupling from between model and highlighter
> (the highlighter is shareable and doesn't care about who applies it to
> which renderer). But there is change notification about highlighter
> state changes, so in a sequence:
>
> [code]
> // adding a highlighter makes the table listen
> // to highlighter state changes
> table.addHighlighter(someHighlighter);
> someHighlighter.setSomeColor(...);
> [/code]
>
> will trigger the table to repaint itself.

Yeah, I figured this must already be tied in. What I meant was that if
any rendering attributes are changed, the table (view) must be
notified about this (that the renderer needs to be re-read, so to
speak). Sorry if I am confused on the issue...

> how did I know you would come up with a considerably longer wishlist
> . Great!

What other purpose to I serve here :)?

> Currently the chain of responsibility is in the HighlighterPipeline and
> externally invoked by the component (or whoever is doing a
> prepareRenderer or equivalent). Your use-case would be solved by adding
> an AlternateH and a ConditionalHighlighter which checks the cell value
> for negative number before changing the foreground. Hmm, do you suggest
> to literally move it into the renderer itself? Not sure if I would like
> that ...

I need to look at how the current HP works...*sigh* so little time...

I just took a quick look at the current approach. So
HighlighterPipeline acts as a decorator (literally!) to a renderer.
What I am thinking is the reverse, where we offer a
DefaultTableCellRendererX which calls out to highlighters to control
things like font, color, etc. The idea is that the DTCRX is a
superclass which knows how to control certain visual effects based on
say, conditional criteria, which of course developers can alter in the
subclass. The DTCRX would delegate this to its chain of
"highlighters", each of which could alter different visual properties.
This would make the DTCRX fairly simple in structure, with the
extensibility being pluggable.

>
> Anyway, there are issues with the "chain" - it turned out to be a bit
> difficult (read: I couldn't find a way to do it ;-) to make sure the
> chained changed attributes end up looking nice in the end. Several
> highlighters effecting background may make the text totally unreadable, f.i.

But I think that the case of interacting background colors is just
more difficult, since background color is in some sense more primary
to the display of a component, where font attributes, say, are not. So
maybe there need to be two approaches in some sense.

Cheers
Patrick

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

Kleopatra

Hi Patrick,

>
> Yeah, I figured this must already be tied in. What I meant was that if
> any rendering attributes are changed, the table (view) must be
> notified about this (that the renderer needs to be re-read, so to
> speak). Sorry if I am confused on the issue...
>

no problem, I just wanted to make sure we are on the same line. BTW,
below is a quick flashing example - couldn't resist some fun coding .
To run, you'll need the InteractiveTestCase of the swingx test package
in the classpath (I'm too lazy to setup frames manually..)

>
> I just took a quick look at the current approach. So
> HighlighterPipeline acts as a decorator (literally!) to a renderer.
> What I am thinking is the reverse, where we offer a
> DefaultTableCellRendererX which calls out to highlighters to control
> things like font, color, etc. The idea is that the DTCRX is a
> superclass which knows how to control certain visual effects based on
> say, conditional criteria, which of course developers can alter in the
> subclass. The DTCRX would delegate this to its chain of
> "highlighters", each of which could alter different visual properties.
> This would make the DTCRX fairly simple in structure, with the
> extensibility being pluggable.
>
>

Okay, so understood you correctly - but that's not a direction I would
go without very good reason. It put's too much weight into the renderer
itself for my taste. F.i. it makes it difficult to have the same effects
across all cells of the table, independent of which concrete renderer is
responsible for the content. Open to use-cases not or not nicely
solvable with the current approach, of course

>
> But I think that the case of interacting background colors is just
> more difficult, since background color is in some sense more primary
> to the display of a component, where font attributes, say, are not. So
> maybe there need to be two approaches in some sense.
>

Hmm,... actually: no, I don't think so. Usually the font has to be as
consistent as any other visual attribute, controlled by LF. Resetting
style, size, whatever has to be as carefully adjusted relative to the
un-highlighted case as colors.

Interestingly, the example shows that (not really intended, came to a
surprise to me :-) - the basic idea was to have a two-stage alert
highlighting: negative values are colored, negative values below
threshold are bolded and the row starts to flash. As highlighters are
shareable across components the same pipeline is used in a table and a
list view of the data. The "bolding" effect is as expected in winLF
(which is my systemLF), but looks a bit weird in Metal, because now it's
thinning a negative but not below threshold value (the list's default
renderer has a bold font, different from the table). So developers still
have to be very careful when applying visual effects.

With experience, we'll get a better understanding what's really helpful
- currently it's more on the playing side. I would love to see
real-world examples which use highlighting creatively!

Musing ...

Cheers
Jeanette

//------------------ example use of conditional

[code]

/*
* Created on 13.04.2006
*
*/
package org.jdesktop.swingx.decorator;

import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;

import javax.swing.ListModel;
import javax.swing.Timer;
import javax.swing.event.ListDataListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;

import org.jdesktop.swingx.InteractiveTestCase;
import org.jdesktop.swingx.JXFrame;
import org.jdesktop.swingx.JXList;
import org.jdesktop.swingx.JXTable;

public class ConditionalFlash extends InteractiveTestCase {

public class ThresholdHighlighter extends ConditionalHighlighter {
Font onFont;
Font offFont;
boolean useOnFont;
int threshold;

public ThresholdHighlighter(Color cellBackground, Color
cellForeground, int testColumn, int highlightColumn) {
super(cellBackground, cellForeground, testColumn,
highlightColumn);
}

@Override
protected Component doHighlight(Component renderer,
ComponentAdapter adapter) {
Component comp = super.doHighlight(renderer, adapter);
applyFont(renderer, adapter);
return comp;
}

@Override
protected Color computeSelectedForeground(Component renderer,
ComponentAdapter adapter) {
return getForeground();
}

@Override
protected void applyBackground(Component renderer,
ComponentAdapter adapter) {
if (isOn()) super.applyBackground(renderer, adapter);
}

private void applyFont(Component renderer, ComponentAdapter
adapter) {
renderer.setFont(isOn() ? onFont : offFont);
}

@Override
protected boolean test(ComponentAdapter adapter) {
Object value = adapter.getFilteredValueAt(adapter.row,
testColumn);
return (value instanceof Integer) && ((Integer)
value).intValue() < getThreshold();
}

public void setThreshold(int threshold) {
if (getThreshold() == threshold) return;
this.threshold = threshold;
fireStateChanged();

}

private int getThreshold() {
return threshold;
}

public void setFonts(Font offFont, Font onFont) {
this.offFont = offFont;
this.onFont = onFont;
fireStateChanged();
}

public void setOn(boolean on) {
if (on == isOn()) return;
this.useOnFont = on;
fireStateChanged();
}

public void toggleOn() {
setOn(!isOn());
}

private boolean isOn() {
return useOnFont;
}
}

public static void main(String args[]) {
ConditionalFlash test = new ConditionalFlash();
setSystemLF(true);
try {
test.runInteractiveTests();
} catch (Exception e) {
System.err.println("exception when executing interactive
tests:");
e.printStackTrace();
}
}

public void interactiveConditionalFlash() {
int rows = 20;
// a model with random integers in column 0
TableModel model = createDefaultTableModel(rows);
JXTable table = new JXTable(model);
// a threshold highlighter coloring foreground of negative values
ThresholdHighlighter negativeHL = new
ThresholdHighlighter(null, Color.MAGENTA, 0, 0);
negativeHL.setOn(true);
table.addHighlighter(negativeHL);

// a threshold highlighter coloring row background
// of subset of negative values ..
final ThresholdHighlighter backgroundHL = new
ThresholdHighlighter(Color.lightGray, null, 0, -1);
backgroundHL.setThreshold(- rows/4 + 1);
table.addHighlighter(backgroundHL);
// a threshold highlighter bolding the font
final ThresholdHighlighter fontHL = new
ThresholdHighlighter(null, null, 0, 0);
fontHL.setFonts(table.getFont(),
table.getFont().deriveFont(Font.BOLD));
fontHL.setThreshold(- rows/4 + 1);
table.addHighlighter(fontHL);

JXList list = new JXList(createListModelWrapper(model));
list.setHighlighters(table.getHighlighters());

ActionListener l = new ActionListener() {

public void actionPerformed(ActionEvent e) {
backgroundHL.toggleOn();
fontHL.toggleOn();

}

};
new Timer(1000, l).start();

JXFrame frame = wrapWithScrollingInFrame(table, list, "flash...");
frame.setVisible(true);
}

private ListModel createListModelWrapper(final TableModel model) {
ListModel listModel = new ListModel() {

public int getSize() {
return model.getRowCount();
}

public Object getElementAt(int index) {
return model.getValueAt(index, 0);
}

public void addListDataListener(ListDataListener l) {
// TODO Auto-generated method stub

}

public void removeListDataListener(ListDataListener l) {
// TODO Auto-generated method stub

}

};
return listModel;
}

private TableModel createDefaultTableModel(int rows) {
DefaultTableModel model = new DefaultTableModel(rows, 2) {

@Override
public Class getColumnClass(int columnIndex) {
if (getRowCount() > 0) {
Object value = getValueAt(0, columnIndex);
return value != null ? value.getClass() : Object.class;
}
return super.getColumnClass(columnIndex);
}

};
Random random = new Random();
int halfRange = rows/2;
for (int row = 0; row < rows; row++) {
int rnd = halfRange - random.nextInt(rows);
model.setValueAt(rnd, row, 0);
model.setValueAt("value: " + rnd, row, 1);
}
return model;
}
}

[/code]

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

kleopatra
Offline
Joined: 2003-06-11

arrghh... the code formatting didn't work out through the mailing list, so I'll try again:

Hmmm... not really convicing, the forum formatter seems to freak out with inline comments?

[code] [pre]

/*
* Created on 13.04.2006
*
*/
package org.jdesktop.swingx.decorator;

import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;

import javax.swing.ListModel;
import javax.swing.Timer;
import javax.swing.event.ListDataListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;

import org.jdesktop.swingx.InteractiveTestCase;
import org.jdesktop.swingx.JXFrame;
import org.jdesktop.swingx.JXList;
import org.jdesktop.swingx.JXTable;

public class ConditionalFlash extends InteractiveTestCase {

public class ThresholdHighlighter extends ConditionalHighlighter {
Font onFont;
Font offFont;
boolean useOnFont;
int threshold;

public ThresholdHighlighter(Color cellBackground, Color cellForeground, int testColumn, int highlightColumn) {
super(cellBackground, cellForeground, testColumn, highlightColumn);
}

@Override
protected Component doHighlight(Component renderer, ComponentAdapter adapter) {
Component comp = super.doHighlight(renderer, adapter);
applyFont(renderer, adapter);
return comp;
}

@Override
protected Color computeSelectedForeground(Component renderer, ComponentAdapter adapter) {
return getForeground();
}

@Override
protected void applyBackground(Component renderer, ComponentAdapter adapter) {
if (isOn()) super.applyBackground(renderer, adapter);
}

private void applyFont(Component renderer, ComponentAdapter adapter) {
renderer.setFont(isOn() ? onFont : offFont);
}

@Override
protected boolean test(ComponentAdapter adapter) {
Object value = adapter.getFilteredValueAt(adapter.row, testColumn);
return (value instanceof Integer) && ((Integer) value).intValue() < getThreshold();
}

public void setThreshold(int threshold) {
if (getThreshold() == threshold) return;
this.threshold = threshold;
fireStateChanged();

}

private int getThreshold() {
return threshold;
}

public void setFonts(Font offFont, Font onFont) {
this.offFont = offFont;
this.onFont = onFont;
fireStateChanged();
}

public void setOn(boolean on) {
if (on == isOn()) return;
this.useOnFont = on;
fireStateChanged();
}

public void toggleOn() {
setOn(!isOn());
}

private boolean isOn() {
return useOnFont;
}
}

public static void main(String args[]) {
ConditionalFlash test = new ConditionalFlash();
setSystemLF(true);
try {
test.runInteractiveTests();
} catch (Exception e) {
System.err.println("exception when executing interactive tests:");
e.printStackTrace();
}
}

public void interactiveConditionalFlash() {
int rows = 20;

// a model with random integers in column 0
TableModel model = createDefaultTableModel(rows);
JXTable table = new JXTable(model);

// a threshold highlighter coloring foreground of negative values
ThresholdHighlighter negativeHL = new ThresholdHighlighter(null, Color.MAGENTA, 0, 0);
negativeHL.setOn(true);
table.addHighlighter(negativeHL);

// a threshold highlighter coloring row background
// of subset of negative values ..
final ThresholdHighlighter backgroundHL = new ThresholdHighlighter(Color.lightGray, null, 0, -1);
backgroundHL.setThreshold(- rows/4 + 1);
table.addHighlighter(backgroundHL);

// a threshold highlighter bolding the font
final ThresholdHighlighter fontHL = new ThresholdHighlighter(null, null, 0, 0);
fontHL.setFonts(table.getFont(), table.getFont().deriveFont(Font.BOLD));
fontHL.setThreshold(- rows/4 + 1);
table.addHighlighter(fontHL);

JXList list = new JXList(createListModelWrapper(model));
list.setHighlighters(table.getHighlighters());

ActionListener l = new ActionListener() {

public void actionPerformed(ActionEvent e) {
backgroundHL.toggleOn();
fontHL.toggleOn();

}

};
new Timer(1000, l).start();

JXFrame frame = wrapWithScrollingInFrame(table, list, "flash...");
frame.setVisible(true);
}

private ListModel createListModelWrapper(final TableModel model) {
ListModel listModel = new ListModel() {

public int getSize() {
return model.getRowCount();
}

public Object getElementAt(int index) {
return model.getValueAt(index, 0);
}

public void addListDataListener(ListDataListener l) {
// TODO Auto-generated method stub

}

public void removeListDataListener(ListDataListener l) {
// TODO Auto-generated method stub

}

};
return listModel;
}

private TableModel createDefaultTableModel(int rows) {
DefaultTableModel model = new DefaultTableModel(rows, 2) {

@Override
public Class getColumnClass(int columnIndex) {
if (getRowCount() > 0) {
Object value = getValueAt(0, columnIndex);
return value != null ? value.getClass() : Object.class;
}
return super.getColumnClass(columnIndex);
}

};
Random random = new Random();
int halfRange = rows/2;
for (int row = 0; row < rows; row++) {
int rnd = halfRange - random.nextInt(rows);
model.setValueAt(rnd, row, 0);
model.setValueAt("value: " + rnd, row, 1);
}
return model;
}
}

[/pre]
[/code]

Kleopatra

Hi Rich

> *rolling up sleeves* :)
>

sexy bare elbows . Looking forward for the lively debate - next week.
I'm on the run (my wife will kill me if I'm late for our dinner appointment)

So just a quick question to make sure we are talking about the same JXTable:

>
> I've got to stop here and protest. There are hard coded links all throughout JXTable to LinkRenderer. There are several places where "if (renderer instanceof LinkRenderer)" is used, right? That counts as hard coded in my book.
>

in my version (which is current, 1.109 2006/03/16) there are exactly two
"instance of LinkRenderer" - both in the method setDefaultLinkVisitor().
(Adding perceived convenience is mostly wrong, nevertheless even I am
falling for it occasionally ) But that's not what I would call "all
throughout". Anyway, this method will go away with a stripped down
LinkAction, so will your argument. The other two matches for
"LinkRenderer" are to the classname only, because the LinkRenderer is
added as default renderer/editor for the LinkModel.class. That's okay
for me, after all we support links represented by LinkModel - even if
you don't like it - out-of-the-box.

Anything I overlooked?

Happy working and a nice weekend
Jeanette

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

kleopatra
Offline
Joined: 2003-06-11

Hi Rich,

just a short note:

> As I mentioned, all the test cases are passing,

yeah - but the visuals are crooked. Run f.i. JXHyperlinkTest as an application, the text isn't the description/name but the string representation of the URL. And the related document is never shown (the second in the test model is a real file). What's wrong - probably just a small oversight when adjusting the tests, but it's not me who is going to track it down

Cheers
Jeanette

rbair
Offline
Joined: 2003-07-08

> yeah - but the visuals are crooked. Run f.i.
> JXHyperlinkTest as an application, the text isn't the
> description/name but the string representation of the
> URL. And the related document is never shown (the
> second in the test model is a real file). What's
> wrong - probably just a small oversight when
> adjusting the tests, but it's not me who is going to
> track it down

I'll take a look at it. Stupid netbeans, won't let me run the file as an application... :)

Richard

pietschy
Offline
Joined: 2003-06-10

Hi guys, just thought I'd put in my 2c's (from a customer/user perspective (c: ).

> I see your point, though I disagree on the utility of
> LinkModel.

Agree (I too fail to see the utility of the LinkModel with regards to JXHyperlink).

> I didn't know this. Use case #1 is a limited subset of use case #2. When I
> wrote JHyperLink, it was squarly aimed at use case #2, since it conceptually
> encompasses all use cases (what can be more basic than "do some action"?).
> JXHyperlink has no utility to me without gracefully handling use case #2

Agree. To me, use case #2 is the one I'm interested in, use case #1 is just one specific instance of #2.

> I have yet to write an application (other than a
> demo) that used JEditorPane as the viewer for any
> HTML coming from a URL.
> Could be just me, but I suspect I'm not in the
> minority. Especially with the
> availability of the JDIC WebBrowser.

Agree.

> I don't see any advantage to the current LinkModel. It doesn't represent the
> model for a JXHyperlink very well. Rather, it is a special case of a more
> general LinkModel. From the JavaDoc: "An bean which represents an URL link."
>
> What is vague about LinkAction & URLLinkAction? It doesn't leave any dirty
> details up to the developer. Indeed, rather than being a pain, it is easier
> (one less class to think about!).

Completely agree.

> LinkAction action = new
> ew OpenCustWindowAction();//extends LinkAction
> JXHyperlink link = new JXHyperlink(action);
>
> //...
> JXEditorPane editor = new JXEditorPane();
> URL url = new URL("http://swinglabs.org");
> action = new URLLinkAction(editor, url);
> link = new JXHyperlink(action);
>

That's how I want to use it (c:

Cheers
Andrew

rbair
Offline
Joined: 2003-07-08

Time to bring this thread back.

I spent the evening implementing my API changes for hyperlinks in SwingX, but haven't committed yet (giving Jeanette another chance to debate the API if she wants to :-)). I've both implemented the code and run the tests (I have a couple of questions regarding two tests that appear to not relate any longer, but more on that later).

In summary, the problems I see with the current design/implementation:

* JXTable, JXTree, JXList have hardcoded references to the LinkRenderer. This is nasty
* LinkRenderer doesn't work with any of the core Swing components (JTree, JTable, JList)
* LinkRenderer is also an editor(!). Not sure why...
* LinkModel/LinkAction have overlapping duties
* LinkModel is tied strongly to JEditorPane/URLs
- simple LinkActions that just do an actionPerformed
are not well supported

I propose removing LinkModel and using LinkAction as the model. I also propose removing all of the hard coded logic in JTree, JTable, and JList that refer to LinkRenderer (LinkController, and all the rest). In addition, I went ahead and implemented the LinkRenderer for JTree as well.

The following changes resulted from this:

* JXDatePicker.TodayAction has to extend LinkAction (no other change, no change in logic)
* "hasBeenVisited" removed from JXHyperLink and pushed into the LinkAction
* JXList, JXTable, JXTree have every reference to LinkRenderer removed (except the string name reference in JXTable associating LinkAction with LinkRenderer), including their LinkControllers
* Major refactoring of LinkRenderer such that for any arbitrary JTree/JTable/JList that is passed into one of the getXXXCellRenderer methods, the proper state/event notification is established (with the proper WeakReferences to this state/listeners may be reclaimed)
* added an Actions utility class in the action package
* LinkAction is abstract
- URLLinkAction extends LinkAction (abstract)
- EditorPaneLinkAction extends URLLinkAction
- DefaultLinkAction extends LinkAction (for GUI builders)

As I mentioned, all the test cases are passing, except for 2. The first is "testRolloverRecognition", because using the RolloverProducer won't work any longer (not supported natively by JTree/JTable/JList). I need to fire a mouse event via the JTable instead.

The second test that is failing is testUpdateRendererOnLFChange, because it was not really testing the renderer but the editor. After removing the code dealing with the editor side of LinkRenderer, the method doesn't do anything so I inserted an assertTrue(false) to make sure I don't forget about it.

Richard

kleopatra
Offline
Joined: 2003-06-11

Hi Rich,

D' oooohhh - the gateway is down again. And now I know that it's you who deliberately is turning it off every time you post to this thread Haven't seen your x-mas post until a couple of hours ago, when wondering about the silence on the list, sorry.

> I spent the evening implementing my API changes for
> hyperlinks in SwingX, but haven't committed yet
> (giving Jeanette another chance to debate the API if
> she wants to :-)).

As you know, I love debates, but you'll have to convince me :-) So let's jump into the middle of your arguments:

>
> In summary, the problems I see with the current
> design/implementation:
>
> * JXTable, JXTree, JXList have hardcoded references
> es to the LinkRenderer.

not really, it's not "hard" but a side-effect of a certain laxness:

* JXTable - only in a convenience method when installing the defaultVisitor for a LinkModel.
* JXList - some trickery needed because of tension between highlighters and link support: for the sake of highlighters need to wrap the given renderer, for the sake of the LinkController need to have be a AbstractButton as renderer. Need to formalize the WrappingRenderer anyway, then the LinkController would drill down until it reaches a "real" rendering component.
* JXTree - nothing yet implemented, but would have the same implication as JXList

> * LinkRenderer doesn't work with any of the core
> re Swing components (JTree, JTable, JList)

Certainly true - on the surface:

back to the "surface" argument: there is nothing in the general design of the link support (which actually is a "abstract button support") which is not applicable in core. Full support requires a RolloverProducer, a LinkController, a LinkRenderer - all with clearly bounded (ehemm... not cleanly documented...ehem) responsibilities:

- RolloverProducer: geometrically decide if a mouseEvent might trigger any per-cell mouse over behaviour, and map mouse coordinates into cell coordinates
- LinkController: decide if the cell at the current rollover coordinates require some special treatment, decide which type of behaviour to trigger, and do it if appropriate
- LinkRenderer: render the given value

They are independent of each other: the glue are the cell-coordinates (implemented as client properties) which are created by the producer and used by the controller/renderer. The average developer doesn't need to know about - enable rollover and everything clicks into gear.

There is nothing principally hardcoded which prevents any of those three to be used in core - I don't expect any problem in relaxing references to x into .

> * LinkRenderer is also an editor(!). Not sure
> re why...

the implementation swayed back and forth between (mis?-)using the editing mechanism, if applicable, or not. Currently it's on for JXTable if the column is editable and not used if the column is not editable. Forgot the details, but there were some usability problems (I'm sure you read all the issues reported and solved against hyperlinks :-)

> * LinkModel/LinkAction have overlapping duties

no, they don't - or only if you disregard separation of concerns between view and model

Note: an Action is _not_ the model of a button! Being a model implies that model properties and related view properties are kept in synch, both ways. That's clearly not the case in a button: it's one-way - the button updates itself with action properties on change, but button properties can and do change without effecting the action. Indeed, a button must not change any of the Action's properties directly, the only interaction from the button to the Action is through the actionPerformed (Mustang is add's the interaction through selected, but that's another story - there's a thread in the swing forum about it)

The LinkModel is (meant as, concededly very incomplete) the abstraction of a Hyperlink, like in the html tag with a mixin of Bookmark, that's the visited property. That model's "visited" is not the equivalent to the button's clicked: the latter tries to trigger a visit, but the visit might be unsuccessful. It's up to the model to decide when it would regard itself as "visited", most probably only after a connection had been established.

The relation of a (Link)Action to a (Link)Model is that the action listens to relevant model properties and maps those to appropriate visual button properties. I tend to regard the Action as a kind of Mediator between model and button, with two separate channels: the direction from model to button maps visual properties into view language, the direction from button to action triggers a state change - exclusively via the Command part of the action. The LinkAction as-is delegates the "back channel" to a delegate action.

> * LinkModel is tied strongly to JEditorPane/URLs

that's definitely true for the latter, and one of the reasons for re-visiting the design . But I can't see any coupling the an editorPane, what do I overlook?

> - simple LinkActions that just do an
> o an actionPerformed
> are not well supported
>

100% ack, and we need to evolve that

> I propose removing LinkModel and using LinkAction as
> the model.

as you might guess - having read until here - I wouldn't support that :-) What I definitely support is strip down the LinkAction to barest minimum and remove any coupling to LinkModel. The minimum to be useful (as I see it), would be:

- a clicked (or visited, don't care) property. That's the only new aspect of the LinkAction which a hyperlink button would be aware of
- a delegate action for the command part, default would only set the wrapping action's clicked property, custom delegates can be arbitrarily complex
- a model property as source for visuals, default would use the string representation as NAME

or in a rough code sketch (untested, not compilable):

[code]

LinkAction extends Action
// the source of properties mappable to visuals
Object model;
Action delegate;
void setModel(model) {
// remove listener to old if appropriate
// clear properties related to old
// add listener to new if appropriate
// reset properties from new
// fire propertyChange on "model"
}
void resetPropertiesFromNew() {
putValue(NAME, model.toString())
setVisited(false);
}
void actionPerformed(..) {
getDelegate().actionPerformed(
new ActionEvent(model,....));
}

Action getDelegateAction() {
if (delegate == null) {
delegate = new AbstractAction() {
actionPerformed() {
model.setVisited(true);
}
}}}
return delegate;

}
}

[/code]

With that in place, there's no need for JXHyperlink to play dirty - it's sole interaction in the direction of action is the actionPerformed, leaving it to LinkAction to update its clicked property, as it deems appropriate. A most bare-bone LinkRenderer is no longer restricted to LinkModel as well - can set any object as model as well as any action as delegate. More specialized LinkActions/Renderers are free to get as complex as they want.

> I also propose removing all of the hard
> coded logic in JTree, JTable, and JList that refer to
> LinkRenderer (LinkController, and all the rest).

assuming you mean their X-counterparts: there is no logic hardcoded to the renderer :-) The controller is hardcoded to the LinkModel - which I agree must be removed. The control logic is needed somewhere, and - not surprisingly - I think it's exactly where it should be: on the component level. No way to put logic into a renderer - they are meant to be dumb.

> In
> addition, I went ahead and implemented the
> LinkRenderer for JTree as well.
>

great!

On the whole, I'm unconvinced (to put it mildly) about both necessity and direction of this major redesign . I feel reasonable comfortable with what we have (which is not accidental,btw :-) - and am pretty sure that we can smooth the rough edges within its boundaries.

That said, how about opening a branch where we can play with the options? You should add some coded use-cases, so we can compare notes on concrete examples.

Cheers
Jeanette

rbair
Offline
Joined: 2003-07-08

Hey Jeanette,

*rolling up sleeves* :)

> > In summary, the problems I see with the current
> > design/implementation:
> >
> > * JXTable, JXTree, JXList have hardcoded
> references
> > es to the LinkRenderer.
>
> not really, it's not "hard" but a side-effect of a
> certain laxness:

I've got to stop here and protest. There are hard coded links all throughout JXTable to LinkRenderer. There are several places where "if (renderer instanceof LinkRenderer)" is used, right? That counts as hard coded in my book.

This is bad, IMO.

Its not just the direct references that are bad, its also all the surrounding logic to support it. Specifying the LinkVisitor on the component is really gross to me too, because it means that adding a new renderer required API changes in the components. For a renderer! The alternate approach I've taken not only works, but without any API on JXTable, JXList, JXTree, etc.

> > * LinkRenderer doesn't work with any of the core
> > re Swing components (JTree, JTable, JList)
>
>
> Certainly true - on the surface:
>

Yes, but the current design precludes use with JTable/JTree/JList, mine does not. When discussing the relative merits of the APIs/implementations, we have to keep in mind that the proposed changes will work perfectly with the core components, while the current design does not.

This is an essential part of the argument -- should a renderer implementation require API changes on the component? My position is an emphatic NO. And, as it turns out, there is no practical reason why a renderer needs to have API support in the component.

> back to the "surface" argument: there is nothing in
> the general design of the link support (which
> actually is a "abstract button support") which is not
> applicable in core. Full support requires a
> RolloverProducer, a LinkController, a LinkRenderer -

And that's my exact problem. I'm not saying it couldn't be moved into Swing, I'm saying it should not have to be. (As an aside, I think RolloverProducer is awesome on its own and I'd want to keep it around. But I wouldn't use it as part of LinkRenderer because it means LinkRenderer won't work with core Swing components).

> - LinkController: decide if the cell at the current
> rollover coordinates require some special treatment,
> decide which type of behaviour to trigger, and do it
> if appropriate

My problem with this is: how does the LinkController decide whether the cell requires special treatment? Why put this logic in the component when the cell renderer already knows this information? By putting this logic in the component, not only does it complicate the implementation, but it also requires support in the component to work. Again, I can't use this with core swing components (or any extended Swing components from third party vendors).

> They are independent of each other: the glue are the
> cell-coordinates (implemented as client properties)
> which are created by the producer and used by the
> controller/renderer. The average developer doesn't
> need to know about - enable rollover and everything
> clicks into gear.

Well, enable rollover, set the cell renderer, and set a LinkVisitor.

I'd rather just set the cell renderer (which knows what to do when clicked) and be done with it.

> > * LinkRenderer is also an editor(!). Not sure
> > re why...
>
> the implementation swayed back and forth between
> (mis?-)using the editing mechanism, if applicable, or
> not. Currently it's on for JXTable if the column is
> editable and not used if the column is not editable.
> Forgot the details, but there were some usability
> problems (I'm sure you read all the issues reported
> and solved against hyperlinks :-)

I will have to experiment a bit more here. It seems to me that hyperlink != editable ever, so I'm happy to ignore this point. But to be fair and prove this approach is not deficient in any way, I'd have to address this issue without hand waving.

> > * LinkModel/LinkAction have overlapping duties
>
> no, they don't - or only if you disregard separation
> of concerns between view and model

Oh no, here we go again :-)

> Note: an Action is _not_ the model of a button! Being
> a model implies that model properties and related
> view properties are kept in synch, both ways.

Point well taken. I guess now you have to prove that there is anything in JXHyperlink that requires its own model. I see nothing. The URL? Put it in a URLLinkAction.
(Hence no "setURL" on the JXHyperlink).

Thus, for all intents and purposes there is no use for LinkModel.

> The LinkModel is (meant as, concededly very
> incomplete) the abstraction of a Hyperlink, like in
> the html tag with a mixin of Bookmark, that's the
> visited property. That model's "visited" is not the
> equivalent to the button's clicked: the latter tries
> to trigger a visit, but the visit might be
> unsuccessful. It's up to the model to decide when it
> would regard itself as "visited", most probably only
> after a connection had been established.

Why up to the model? Why not up to the Action? If the action is the one performing the click event, then it is also the one that knows whether it has been clicked. And this is just the sort of thing you'd want to share among hyperlinks -- "hey everybody, the user has visited SwingLabs.org, so show yourself as visited!".

I see nothing interesting in LinkModel. It has some state that belongs in a URLLinkAction.

> The relation of a (Link)Action to a (Link)Model is
> that the action listens to relevant model properties
> and maps those to appropriate visual button
> properties. I tend to regard the Action as a kind of
> Mediator between model and button, with two separate
> channels: the direction from model to button maps
> visual properties into view language, the direction
> from button to action triggers a state change -
> exclusively via the Command part of the action. The
> LinkAction as-is delegates the "back channel" to a
> delegate action.

But for what purpose? This is all needless complication unless you can convince me that LinkModel actually performs some function.

- String text: already taken care of by LinkAction AND AbstractButton
- URL url: put it in URLLinkAction
- String target: put it in URLLinkAction or some suitable subclass
- boolean visited: belongs in LinkAction

Of these, only the "visited" property sounds like something that I'd want API on JXHyperlink for (which suggests that maybe it does belong in a model), but that is not worth the API complexity required to support it.

Ok, you'll argue about the name property too, I know. But when I am using an Action, I don't care about the name (taken care of by the action). And if I'm not using an action, I can set the name directly on the JXHyperlink (setText(), just like with a button). What's the point of having it in a new model?

> > * LinkModel is tied strongly to JEditorPane/URLs
>
> that's definitely true for the latter, and one of the
> reasons for re-visiting the design . But I can't
> see any coupling the an editorPane, what do I
> overlook?

You're right. Instead it requires yet another level of indirection via the setLinkVisitor or some such mechanism :-). Its still awkward to have in LinkModel, and awkward for a developer to use.

> - a clicked (or visited, don't care) property. That's
> the only new aspect of the LinkAction which a
> hyperlink button would be aware of
> - a delegate action for the command part, default
> would only set the wrapping action's clicked
> property, custom delegates can be arbitrarily
> complex

Why a delegate? Why not let LinkAction be abstract and let developers subclass and implement? I also suggest a DefaultLinkAction which fires action performed events, which make it trivial to use from a GUI builder. But that's another discussion.

A delegate here feels wrong. I have an Action delegating to an Action.

> > I also propose removing all of the hard
> > coded logic in JTree, JTable, and JList that refer
> to
> > LinkRenderer (LinkController, and all the rest).
>
>
> assuming you mean their X-counterparts: there is no
> logic hardcoded to the renderer :-) The controller is
> hardcoded to the LinkModel - which I agree must be
> removed. The control logic is needed somewhere, and -
> not surprisingly - I think it's exactly where it
> should be: on the component level. No way to put
> logic into a renderer - they are meant to be dumb.

Components are not meant to be hard-coding logic to their renderers. In fact, the logic belongs in the cell renderer because:
* Determining whether to draw an underline or not is part of the renderer's concern
* Determining what hand cursor to use is the renderers concern

By moving that responsibility out to the component we end up with all the hard coded references to LinkRenderer and the scattered logic. To understand and use LinkRenderer in its current state I have to:
* Set the renderer on the component
* Turn on rolloverEnabled
* Set a LinkVisitor on the component

In my view I should simply
* Set the renderer on the component

And let the renderer deal with the rest. That seems more in line with expected renderer behavior. I'm not aware of creating renderers anywhere else in Swing that require toggling API on the component as well. Could be overlooking something though.

> On the whole, I'm unconvinced (to put it mildly)
> about both necessity and direction of this major
> redesign . I feel reasonable comfortable with what
> we have (which is not accidental,btw :-) - and am
> pretty sure that we can smooth the rough edges within
> its boundaries.

That's what makes this so productive, actually. If we both started from the same point of agreement we might be overlooking something. Being so radically opposed is probably a good thing :).

I have fundemental problems with the current design.
* JX* should not have any special API to handle a new renderer
* There is no justification for LinkModel
* Its hard to use -- to many moving pieces
* It requires to much conceptual knowledge from the developer (create a link visitor, a link model, a link action, a LinkRenderer, set on table, configure rollover)

> That said, how about opening a branch where we can
> play with the options? You should add some coded
> use-cases, so we can compare notes on concrete
> examples.

I thought about doing it last night, but figured I'd fire the first salvo first and see what happens :). I'll commit it to a branch so you can inspect the code and run some examples.

Have a good weekend!
Richard

kleopatra
Offline
Joined: 2003-06-11

Not sure if list --> forum works again, so I'll copy my latest post to it:

Hi Rich

>> *rolling up sleeves* :)
>>

sexy bare elbows . Looking forward for the lively debate - next week.
I'm on the run (my wife will kill me if I'm late for our dinner appointment)

So just a quick question to make sure we are talking about the same JXTable:

>>
>> I've got to stop here and protest. There are hard coded links all throughout JXTable to LinkRenderer. There are several places where "if (renderer instanceof LinkRenderer)" is used, right? That counts as hard coded in my book.
>>

in my version (which is current, 1.109 2006/03/16) there are exactly two
"instance of LinkRenderer" - both in the method setDefaultLinkVisitor().
(Adding perceived convenience is mostly wrong, nevertheless even I am
falling for it occasionally ) But that's not what I would call "all
throughout". Anyway, this method will go away with a stripped down
LinkAction, so will your argument. The other two matches for
"LinkRenderer" are to the classname only, because the LinkRenderer is
added as default renderer/editor for the LinkModel.class. That's okay
for me, after all we support links represented by LinkModel - even if
you don't like it - out-of-the-box.

Anything I overlooked?

Happy working and a nice weekend
Jeanette

rbair
Offline
Joined: 2003-07-08

> >> I've got to stop here and protest. There are hard
> coded links all throughout JXTable to LinkRenderer.
> There are several places where "if (renderer
> instanceof LinkRenderer)" is used, right? That counts
> as hard coded in my book.
>
> in my version (which is current, 1.109 2006/03/16)
> there are exactly two
> "instance of LinkRenderer" - both in the method
> setDefaultLinkVisitor().
> (Adding perceived convenience is mostly wrong,
> nevertheless even I am
> falling for it occasionally ) But that's not what
> I would call "all
> throughout".

Ahah, you are correct. I suppose conceptually I thought of it as "all throughout" because the implications of adding support to JXTable for LinkRenderer required the LinkController, etc. The larger issue is that a lot of API/code was added to support rendering hyperlinks in JXTable etc. whereas I prefer none.

> The other two
> matches for
> "LinkRenderer" are to the classname only, because the
> LinkRenderer is
> added as default renderer/editor for the
> LinkModel.class. That's okay
> for me, after all we support links represented by
> LinkModel - even if
> you don't like it - out-of-the-box.

100% agree. Having default support if a column type is LinkAction ;-) is perfectly alright.

Cheers
Richard

Kleopatra

Hi Rich,

>
> I'm trying to use the LinkRenderer on a JXTable with no success.

looks like you are trying to use the LinkModel/Renderer
out-of-specification scope (or I mis-read your example :-) As is, the
LinkModel is tighltly coupled to the notion of an URL - see Mark's
comment to issue #37-jdnc which still applies. A LinkAction is the glue
between a LinkModel and a JXHyperlink (taking care of synching the
properties) and can be given a custom ActionListener (the
"visitingDelegate") which is triggered on LinkAction's actionPerformed,
the associated LinkModel is passed as the event source of the generated
actionEvent. A pre-defined delegate action is the EditorPaneLinkVisitor
which opens the target url in the given editorPane. Should all be
documented properly, *sigh*.

As you remember, there have been several threads about "hyperlinks" -
the "navigate to arbitrary targets" (like opening custom dialogs f.i.)
came out as a different notion (as compared to accessing/opening urls)
and postponed - needs further abstraction.

Hmmm ... a quick go for a specialized context shouldn't be too difficult
(thinking aloud): what's needed are custom equivalents to LinkModel
(some data to synch with the "hyperlink") and to LinkAction (needs to
know which properties to synch in the special case). Now the
LinkRenderer needs to be slightly refactored to leave the creation and
actual value setting of/in the LinkAction equivalent to subclasses
(and/or some pluggable handler). Could do that one of these days if you
need it - but maybe better stick to showing what we can do now, not what
we intend to support in future ;-)

BTW, by default the JXHyperlink does show the underline only on rollover
(it's not yet decided if that should really be the "default" - and
adding a property to toggle the default whatever is not yet implemented,
my fault, simply forgot about it). To enable the rollover underline in a
JXTable/JXList you have to set its rolloverEnabled property to true.

HTH, but feel free to nail me!

Jeanette

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

Richard Bair

Hey Jeanette,

In my (admittedly brief) perusal of the code, it seems to me that both
LinkAction and LinkModel are both performing the same function -- that of
"model" for the JXHyperlink. IMO the two ought to be combined. Personally, I
don't see the value in the LinkModel (never have).

There are 2 basic use cases for JXHyperlink
1) fetching & possibly displaying some resource (traditional URL)
2) performing some action, possibly showing a window (more like a button)

LinkModel:
[code]
private String text; // duplicate of the text property in LinkAction
private URL url; // useful only for use case #1
private String target; // useful only for a subset of use case #1
private boolean visited = false; //always useful, should be in
LinkAction
[/code]

LinkAction:
[code]
private LinkModel link; //should be removed
private ActionListener delegate; //this is good, supports use case #2
[/code]

And here is how I'd like to see LinkAction changed:
[code]
public class LinkAction extends AbstractAction {
public static final String VISIT_ACTION = "visit";
public static final String VISITED_PROPERTY = "visited";
public static final String URL_PROPERTY = "url";
public static final String TARGET_PROPERTY = "target";

private ActionListener delegate;

public LinkAction() {
}

public LinkAction(String text, String target, URL url) {
putValue(Action.NAME, text);
putValue(Action.SHORT_DESCRIPTION, url.toString());
putValue(URL_PROPERTY, url);
putValue(TARGET_PROPERTY, target);
putValue(VISITED_PROPERTY, false);
}

//various setter/getter methods

public void setVisitingDelegate(ActionListener delegate) {
this.delegate = delegate;
}

public void actionPerformed(ActionEvent e) {
if (delegate != null) {
delegate.actionPerformed(
new ActionEvent(this, ActionEvent.ACTION_PERFORMED,
VISIT_ACTION));
} else {
//fetch the data at the given URL, display in the proper form,
or whatever needs
//to be done for use case #1
}
}
}
[/code]

Alternatively, use case #1 can be extracted from LinkAction and placed into
URLLinkAction or something like that, allowing LinkAction to simply handle
the "visited" property and a delegate Action listener (as opposed to
subclassing LinkAction, which might be preferrable to a delegate, I'm not
opinionated on that).

In this way, both use cases should be handled with the least amount of
effort. LinkRenderer could then take LinkAction as opposed to
visitingDelegate, and we should be done.

Thoughts?
Richard

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

Kleopatra

Hi Richard,

>
> In my (admittedly brief) perusal of the code, it seems to me that both
> LinkAction and LinkModel are both performing the same function -- that of
> "model" for the JXHyperlink. IMO the two ought to be combined. Personally, I
> don't see the value in the LinkModel (never have).
>

brewing a big hot stew again .

No I don't view them as performing the same function - they differ in
their role: the LinkModel is "data" and the LinkAction (as a typical
action) is a hybrid at the borderline between "controller" (a Command
consisting of the performed and enabled) and "view", the latter fed by
certain "data" properties.

A LinkModel can be used independently of a JXHyperlink - it caries all
information to open the linked document in an editorPane, f.i. The other
way round: a JXHyperlink doesn't care which concrete type of action it
has set - leaving the option to hook into custom
XXModel/XXAction/XXDelegateAction triads (or replace them by a custom
single action if you insist )

As I said before: the current LinkYY is heavily biased towards your
use-case #1 (to focus on that first was a conscious decision :-). The
open question is how well it can be extended towards use-case #2
_without_ loosing the type-safety of a dedicated XXModel! I would really
hate to mix it back into some vague notion of "target" as the
HyperlinkListener in the text package does - leaving all the dirty
details to every client implementing it, which is a pain to develop
against.

Anyway, there's enough leeway to experiment a bit - the "bottleneck"
being the LinkRenderer: it's (probably overly) fixated on expecting a
LinkModel. I'll have to think a bit about how to open it up so it can
handle other XX (or even a custom OneDoesItAllAction :-) smoothly.

CU
Jeanette

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

Richard Bair

Hey Jeanette,

>No I don't view them as performing the same function - they differ in their
>role: the LinkModel is "data" and the LinkAction (as a typical action) is a
>hybrid at the borderline between "controller" (a Command consisting of the
>performed and enabled) and "view", the latter fed by certain "data"
>properties.

I see your point, though I disagree on the utility of LinkModel.

>A LinkModel can be used independently of a JXHyperlink - it caries all
>information to open the linked document in an editorPane, f.i. The other
>way round: a JXHyperlink doesn't care which concrete type of action it has
>set - leaving the option to hook into custom
>XXModel/XXAction/XXDelegateAction triads (or replace them by a custom
>single action if you insist )

A LinkAction can be used independently of a JXHyperlink - it (could) caries
all information to open the linked document in an editorPane :). Or more
appropriately, there should be a LinkAction and a URLLinkAction (or
EditorPaneLinkAction) subclass.

>As I said before: the current LinkYY is heavily biased towards your
>use-case #1 (to focus on that first was a conscious decision :-).

I didn't know this. Use case #1 is a limited subset of use case #2. When I
wrote JHyperLink, it was squarly aimed at use case #2, since it conceptually
encompasses all use cases (what can be more basic than "do some action"?).
JXHyperlink has no utility to me without gracefully handling use case #2 turn to duck for cover :)> I have yet to write an application (other than a
demo) that used JEditorPane as the viewer for any HTML coming from a URL.
Could be just me, but I suspect I'm not in the minority. Especially with the
availability of the JDIC WebBrowser.

>The open question is how well it can be extended towards use-case #2
>_without_ loosing the type-safety of a dedicated XXModel! I would really
>hate to mix it back into some vague notion of "target" as the
>HyperlinkListener in the text package does - leaving all the dirty details
>to every client implementing it, which is a pain to develop against.

I don't see any advantage to the current LinkModel. It doesn't represent the
model for a JXHyperlink very well. Rather, it is a special case of a more
general LinkModel. From the JavaDoc: "An bean which represents an URL link."

What is vague about LinkAction & URLLinkAction? It doesn't leave any dirty
details up to the developer. Indeed, rather than being a pain, it is easier
(one less class to think about!).

[code]
LinkAction action = new OpenCustWindowAction();//extends LinkAction
JXHyperlink link = new JXHyperlink(action);

//...
JXEditorPane editor = new JXEditorPane();
URL url = new URL("http://swinglabs.org");
action = new URLLinkAction(editor, url);
link = new JXHyperlink(action);

//...
JXTable table = new JXTable(/*...*/);
TableColumn col = table.getColumnExt("order_number");
action = new OpenOrdersWindowAction();
col.setCellRenderer(new LinkRenderer(action));
[/code]

Cheers
Richard

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

Kleopatra

Hi Richard,

this looks like our usual disagreement - I think it's conceptually
unwise to mix data and view and I'm generally against overstuffing
objects. An Action has a nicely defined boundary in the view/controller
realm, using it as a (untyped) data-store for URLs f.i. or any other
garden variety of user data feels definitely wrong to me.

That said, we can keep the decision open for now and you implement your
way of "compound handling" as an alternative - we'll see how useful the
one or other might be in the longer run. No hard feelings at this side
of the ocean :-)

Have a nice weekend
Jeanette

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

Richard Bair

Hi Jeanette,

>this looks like our usual disagreement - I think it's conceptually unwise
>to mix data and view and I'm generally against overstuffing objects. An
>Action has a nicely defined boundary in the view/controller realm, using it
>as a (untyped) data-store for URLs f.i. or any other garden variety of user
>data feels definitely wrong to me.

I guess I just don't see the Action as mixing data and view. It's all view
to me. I also don't see it as "overstuffing". And, I don't understand the
"untyped" argument. If URLLinkAction has a "getUrl" method, then how is that
untyped? That the data is being stored internally in a Map is an
implementaton detail (which is regrettably exposed via the Action
interface).

(I know all the following explanation is something you are very aware of,
but to help make sure we are on the same page, I list it. Concrete examples
help me think)

For example, using the PresentationModel pattern. My PM would have a
"getVendorURL" method. The GUI would, during initialization, call
"getVendorURL", construct a URLLinkAction (possibly invoking other methods
on the PM to get the Vendor name, etc) and set it as the action for my
JXHyperlink (or LinkRenderer).

Similarly, if I were to populate a JList with all the vendors, my PM would
perhaps return a List from a "getVendors" method. The GUI (perhaps
via some binding framework, or by creating a ListModel of its own) would
populate the JList after calling "getVendors" from the PM.

I fail to see (or, in other words, help me see) how URLLinkAction mixes data
and view. To me, Actions are models. Whereas ButtonModel handles things like
rollover, armed, pressed, etc. Action handles text, image, selected, and so
on. So AbstractButton has potentially two different models, the intersection
of ButtonModel and Action.

Even ButtonModel has an "addActionListener" method, indicating that it is
conceptually similar to Action. So again, I don't see where view & data is
being mixed, or how URLLinkAction is any less typesafe than LinkAction +
LinkModel.

>That said, we can keep the decision open for now and you implement your
>way of "compound handling" as an alternative - we'll see how useful the one
>or other might be in the longer run. No hard feelings at this side of the
>ocean :-)

None here either :-). Especially since I'll be in town next week and don't
want to get beat up ;)

Practically, I don't see how we can have both implementations. Certainly not
in SwingX. I can put it in the incubator, and that would be a good initial
first step. In the near term, however, we will have to resolve this.

Cheers
Richard

---------------------------------------------------------------------
To unsubscribe, e-mail: jdnc-unsubscribe@jdnc.dev.java.net
For additional commands, e-mail: jdnc-help@jdnc.dev.java.net

rbair
Offline
Joined: 2003-07-08

Hey Jeanette,

More LinkRenderer headaches to share. As it turns out, the LinkRenderer is quite a tricky problem. At first it doesn't seem like it should be so sticky, but it is. I'm sure all of this is review for you, but for anybody else following along (I'm talking this through from the perspective of a JList, but it is similar JTree and JTable as well):

The ListCellRenderer API allows a single Renderer to be reused among multiple JLists. However, in order for the Hyperlink to look and feel right (changing the cursor to indicate that it can be clicked, providing the underline and possibly changing the text color on mouse over, AND executing an action when clicked) a mouse listener must be placed on the hosting component (ie: the JList that the cell renderer is used on). This leaves three general approaches:

First, we can mandate that a LinkRenderer works with only one JList. It is then trivial to code the LinkRenderer to register a mouse listener on the JList. In the vast majority of cases, this will probably be what the user does anyway. It also works with JList or JXList. Its strength is also a weakness -- you couldn't create a generic URL LinkRenderer that would display clicked URLs in a web browser and reuse it throughout your application.

Second, we can add logic to the JXList so that it handles the mouse motion logic for the renderer (which is essentially what RolloverProducer does -- which is a really cool idea, BTW). The JXList would also then be responsible for handling the click event. This is where it gets nasty. The benefit is that the Renderer could be shared among multiple JXLists. The problem is that coding a dependency in JXList for a specific renderer is ugly. Also, the renderer won't work with ordinary JLists.

The third approach is to maintain a WeakHashMap of JLists in the renderer. Whenever "getListCellRendererComponent" is called, if the JList isn't in the HashMap, then attach a mouse listener and add it. Whenever a reference to a JList needs to be garbage collected, then get rid of the mouse listener (which could happen automatically if done correctly). In this way, the LinkRenderer can work with multiple JLists, doesn't require JXList, and doesn't require an explicit reference to the list when constructed. The downside is complexity of implementation.

As a user I prefer #3. #1 is obviously simpler to code (although it would require a ListLinkRenderer, TableLinkRenderer, and TreeLinkRenderer since you have to pass in the component reference in the constructor). #1 is also going to be much easier to get right, while #3 is a memory leak waiting to happen (unless carefully done).

Also, we (regrettably) shouldn't use RolloverProducer in the LinkRenderer implementations because then JList/JTree/JTable won't work properly (unless the developer adds a RolloverProducer to the component first, but we shouldn't require that step. Also, it means that the table has to have "rolloverEnabled" set to true, which is redundant -- I know I want it to be rolloverEnabled because I put a LinkRenderer on it. Etc. Etc.). The only clean way to do it is to have the LinkRenderer register its own MouseAdapter on the component to keep track of rollover, clicks, etc.

No solutions here from me, just the problems :). I'd vote for #1, but the perfectionist in me can't help but take a crack on #3.

Oh, and I'm not sure we want all of the LinkRenderer implementations bundled into one "LinkRenderer" class -- but that's not a huge issue for me.

Richard