Bookstore

 Computer Math and Games in Pascal (preview) Lazarus Handbook

Author Topic: 'Black on black' combobox items with certain *nix desktops  (Read 2167 times)

Sieben

• Full Member
• Posts: 215
'Black on black' combobox items with certain *nix desktops
« on: December 23, 2020, 03:48:34 pm »
There is an annoying problem with items of TCustomComboBox and descendants like TColorBox being painted 'black on black' with certain *nix desktops which has already been discussed here and here which I might have found a solution to.

Found a piece of code that calculates the 'intensity' of a background color like this:

Code: Pascal  [Select][+][-]
1. function Intensity(AColor: TColor): Integer;
2. begin
3.   AColor := ColorToRGB(AColor);
4.   Result := GetRValue(AColor) * 61
5.           + GetGValue(AColor) * 174
6.           + GetBValue(AColor) * 21;
7. end;

and suggests a threshold of 32k for deciding whether to use a dark or a bright font color. Code based on this tested ok for TCustomComboBox.DrawItem as well as for overriden methods in descendants. Drawback is of course that every overridden method has to be aware of this pitfall. So I tried 'injecting' this into TCustomComboBox.LMDrawListItem prior to the call of DrawItem instead:

Code: Pascal  [Select][+][-]
1. function IsDarkBackground(AColor: TColor): Boolean;
2. begin
3.   AColor := ColorToRGB(AColor);
4.   Result := (GetRValue(AColor) * 61
5.            + GetGValue(AColor) * 174
6.            + GetBValue(AColor) * 21) <= (128 * 256);
7. end;
8.
9. {------------------------------------------------------------------------------
10.   procedure TCustomComboBox.LMDrawListItem(var TheMessage : TLMDrawListItem);
11.
12.   Handler for custom drawing items.
13.  ------------------------------------------------------------------------------}
14. procedure TCustomComboBox.LMDrawListItem(var TheMessage : TLMDrawListItem);
15. begin
16.   with TheMessage.DrawListItemStruct^ do
17.   begin
18.     FCanvas.Handle := DC;
19.     if Font<>nil then
20.     begin
21.       FCanvas.Font := Font;
22.       FCanvas.Font.PixelsPerInch := Font.PixelsPerInch;
23.     end;
24.     if Brush<>nil then
25.       FCanvas.Brush := Brush;
26.     if (ItemID <> UINT(-1)) and (odSelected in ItemState) then
27.     begin
28.       FCanvas.Brush.Color := clHighlight;
29.       FCanvas.Font.Color := clHighlightText
30.     end;
31.
32.     if DroppedDown                            //injection...
33.     and (odBackgroundPainted in ItemState)
34.     and IsDarkBackground(Canvas.Pixels[1,1]) then
35.       Canvas.Font.Color := clHighLightText;
36.
37.     DrawItem(ItemID, Area, ItemState);
38.     {if odFocused in ItemState then
39.       DrawFocusRect(hDC, rcItem)};
40.     FCanvas.Handle := 0;
41.   end;
42. end;

This seems to work for every descendant of TCustomComboBox now. All an overridden DrawItem or custom OnDrawItem handler has to take care of then is not to just repaint the background but instead to use code like this:

Code: Pascal  [Select][+][-]
1.     if not (odBackgroundPainted in AState) then
2.       Canvas.FillRect(ARect);

as suggested by the 'original' DrawItem anyway. And not to reassign font colors of course as this is done already as well.

I don't know, however, if and how this would affect any other widgetset but I think it is quite likely that it won't. I'm including a diff file done with trunk r64267 so that anyone who might be interested can test this. Some feedback would be nice...

EDIT: removed diff file for nonfug, cf below.
« Last Edit: December 23, 2020, 09:56:23 pm by Sieben »
Lazarus 2.0.10, FPC 3.2.0, .deb install on Ubuntu Xenial 32 / Gtk2 / Unity7

wp

• Hero Member
• Posts: 8774
Re: 'Black on black' combobox items with certain *nix desktops
« Reply #1 on: December 23, 2020, 07:05:08 pm »
Your code detects whether the background color is dark and then you draw the text in clHighLightText. But clHighlighttext is a themed color, it can be anything. How do you know that clHighlightText isn't dark either? Normally the system colors are well-balanced, there are only a few cases (Ubunto 16.04 and 18.04, and maybe some more) where the theme designers used their own rules.
Mainly Lazarus trunk / fpc 3.2.0 / all 32-bit on Win-10, but many more...

Sieben

• Full Member
• Posts: 215
Re: 'Black on black' combobox items with certain *nix desktops
« Reply #2 on: December 23, 2020, 07:31:34 pm »
It's just an assumption that served my tests here I have to admit, and it also depends on whether the canvas comes 'pre-painted' with other widgetsets. But it could easily be replaced with eg clWhite if it proves wrong. Or another test for the brightness of clWindowText vs clHighLightText.
Lazarus 2.0.10, FPC 3.2.0, .deb install on Ubuntu Xenial 32 / Gtk2 / Unity7

winni

• Hero Member
• Posts: 2667
Re: 'Black on black' combobox items with certain *nix desktops
« Reply #3 on: December 23, 2020, 08:31:38 pm »
Hi!

Don't rely on assumptions on system colors.

Use the complement of the given color:

Code: Pascal  [Select][+][-]
1. function  Complement (col: TColor): TColor;
2. begin
3. Result := \$FFFFFF - ColorToRGB(col);
4. end;

Only one trap:
If your input is in the midrange gray the output will also be in the midrange gray.

Winni

Sieben

• Full Member
• Posts: 215
Re: 'Black on black' combobox items with certain *nix desktops
« Reply #4 on: December 23, 2020, 09:54:27 pm »
Thanks again, this would at least result in a readable font color, but still doesn't look seamless. clWhite would do a slightly better job in this case. But the main misapprehension I took, and I don't really know why right now, was that this early background painting is unique to this specific widgetset.

Now, what assumptions can be made with this issue...?

1) Font color used for item painting will always be either clWindowText or clHighLighText...?

2) Background is already painted, ie odBackgroundPainted in ItemState...?

3) Background is painted in a 'dark' color while Canvas.Brush.Color, indicating the controls 'normal' color, ist not...?

Code: Pascal  [Select][+][-]
1.     if DroppedDown
2.     and IsDark(Canvas.Pixels[1,1])
3.     and not IsDark(Canvas.Brush.Color)
4.     and (odBackgroundPainted in ItemState) then
5.     begin
6.       if IsDark(clWindowText) then
7.         Canvas.Font.Color := clHighLightText
8.       else
9.         Canvas.Font.Color := clWindowText;
10.     end;

Are there any more conditions besides 2) and 3) to not interfere with font color that can be tested for?
Lazarus 2.0.10, FPC 3.2.0, .deb install on Ubuntu Xenial 32 / Gtk2 / Unity7

winni

• Hero Member
• Posts: 2667
Re: 'Black on black' combobox items with certain *nix desktops
« Reply #5 on: December 24, 2020, 05:09:22 pm »
Hi Sieben!

I can't test your code because I get an compiler error:

bigimage1.pas(102,5) Error: Internal error 2014010312
It shows the line with the begin
Code: Pascal  [Select][+][-]
1. {------------------------------------------------------------------------------
2.   procedure TCustomComboBox.LMDrawListItem(var TheMessage : TLMDrawListItem);
3.    Handler for custom drawing items.
4.  ------------------------------------------------------------------------------}
5. procedure TCustomComboBox.LMDrawListItem(var TheMessage : TLMDrawListItem);
6. begin   // <=====
7.

fpc 3.04  laz 2.0.10 gtk2 lin64

Winni

Sieben

• Full Member
• Posts: 215
Re: 'Black on black' combobox items with certain *nix desktops
« Reply #6 on: December 27, 2020, 07:24:14 pm »
Quote from: winni
I can't test your code because I get an compiler error:

I'm afraid there is not much I can do about that...

But to continue with this little saga - two or maybe three more conditions:

4) only non-selected items are concerned

5) happens only with certain Linux desktops

6) - unfortunately I'm not sure here - happens only with Gtk2

Is there anyone out there who can confirm this really is an issue restricted to Gtk2 only? And anyone knows why

Code: Pascal  [Select][+][-]
1. {\$IFDEF LCLGTK2}
2. // code here...
3. {\$ENDIF}

does not gray out the code in between in the editor but doesn't compile it either...?

Edit: Sorry for my premature posting earlier this week - I was so absorbed by this graphics stuff and that I finally seemed to have a hold on that that I totally forgot on the conditions when to interfere with font color settings at all.
« Last Edit: December 27, 2020, 07:51:03 pm by Sieben »
Lazarus 2.0.10, FPC 3.2.0, .deb install on Ubuntu Xenial 32 / Gtk2 / Unity7

wp

• Hero Member
• Posts: 8774
Re: 'Black on black' combobox items with certain *nix desktops
« Reply #7 on: December 27, 2020, 07:57:50 pm »
Code: Pascal  [Select][+][-]
1. {\$IFDEF LCLGTK2}
2.   WriteLn(1+);   // Note the missing '1' at the end.
3. {\$ENDIF}
Above snippet will not compile if you are on gtk2. It will compile, when you replace the WriteLn argument by (1+1).

But note that gtk2 alone is not your issue because there are other Linux systems using gtk2 where your issue does not occur. You must also detect the type and version of your Linux system, eg. 'Ubuntu 16.04' - Linux specialists will be able to tell you how to do this from Lazarus.
Mainly Lazarus trunk / fpc 3.2.0 / all 32-bit on Win-10, but many more...

Sieben

• Full Member
• Posts: 215
Re: 'Black on black' combobox items with certain *nix desktops
« Reply #8 on: December 27, 2020, 09:33:08 pm »
Quote from: wp
Above snippet will not compile if you are on gtk2. It will compile, when you replace the WriteLn argument by (1+1).

Hm, what's the rule behind that...? It seems I can't get any code to compile in this here context with said condition...

Quote from: wp
But note that gtk2 alone is not your issue because there are other Linux systems using gtk2 where your issue does not occur. You must also detect the type and version of your Linux system, eg. 'Ubuntu 16.04' - Linux specialists will be able to tell you how to do this from Lazarus.

What about 'SESSION=Ubuntu', 'XDG_CURRENT_DESKTOP= Unity' or 'UPSTART_JOB=unity7' (environment variables) or any sensible combination as a valid condition for this? Can you possibly confirm that any of these are true as well with the 18.04 Ubuntu you installed, but not for other linux installations?

That said it would of course be most desirable to have a sufficient condition, but if it turns out there is none a number of necessary conditions must be sought that a) turn down the probability of interfering without necessity and b) even if so to not make any settings that hurt. For the latter it would help to know if condition 1) as mentioned earlier is true: font color for this will in any case either be clWindowText or clHighLightText, tertium non datur.
Lazarus 2.0.10, FPC 3.2.0, .deb install on Ubuntu Xenial 32 / Gtk2 / Unity7

wp

• Hero Member
• Posts: 8774
Re: 'Black on black' combobox items with certain *nix desktops
« Reply #9 on: December 27, 2020, 10:57:34 pm »
Quote from: wp
Above snippet will not compile if you are on gtk2. It will compile, when you replace the WriteLn argument by (1+1).

Hm, what's the rule behind that...? It seems I can't get any code to compile in this here context with said condition...
The idea is that when you are on gtk2 the compiler sees the code within the{\$IFDEF LCLGTK2}..{\$ENDIF}. To test this you can put some faulty code in there, and the compilation will stop here.

What about 'SESSION=Ubuntu', 'XDG_CURRENT_DESKTOP= Unity' or 'UPSTART_JOB=unity7' (environment variables) or any sensible combination as a valid condition for this? Can you possibly confirm that any of these are true as well with the 18.04 Ubuntu you installed, but not for other linux installations?
First of all: I am not a Linux man, so maybe this is far easier than what I am saying...

Anyway, here is the excerpt of the env command in all my Linuxes:

Ubuntu 16.04 (Combobox behaviour as you describe)
SESSION=ubuntu
XDG_CURRENT_DESKTOP=Unity
UPSTART_JOB=Unity7

Ubuntu 18.04 (Combobox behaviour as you describe)
GDMSESSION=ubuntu
XDG_CURRENT_DESKTOP=ubuntu:GNOME

Ubuntu 20.10 (Combobox behaviour CORRECT! But LCL system colors are not extracted from theme correctly: In dark mode, clWindow is still bright, but the background of TCombobox or TEdit is dark -- what a mess!)
GDMSESSION=ubuntu
XDG_CURRENT_DESKTOP=ubuntu:GNOME

Lubuntu 20.10 (correct, but I was not able to switch into some kind of "dark mode")
DESKTOP_SESSION=Lubuntu
XDG_CURRENT_DESTKOP=LXQt

LMDE 4 Cinnamon (correct theme support)
GDMSESSION=cinnamon
DESKTOP_SESSION=cinnamon
XDG_CURRENT_DESKTOP=X-Cinnamon

My impression is that theme support in the various Linuxes is much more multi-facetted and is not mapped on the LCL system colors. But as I said, I am not a Linux man...
« Last Edit: December 28, 2020, 12:21:46 am by wp »
Mainly Lazarus trunk / fpc 3.2.0 / all 32-bit on Win-10, but many more...

Sieben

• Full Member
• Posts: 215
Re: 'Black on black' combobox items with certain *nix desktops
« Reply #10 on: December 28, 2020, 12:16:46 am »
Thanks a lot for your effort, I really appreciate that.

Quote from: wp
Anyway, here is the excerpt of the env command in all my Linuxes:

Ok, I'm very close to giving up as well now... perhaps the best 'solution' to this issue is providing some info here and/or the wiki on how to deal with it should someone experience this on a target system. I think this:

Code: Pascal  [Select][+][-]
1.     {\$IFDEF UNIX}          // fix for 'black on black' items
2.     if DroppedDown         // with certain gtk2 unix desktops
3.     and (odBackgroundPainted in ItemState)
4.     and not (odSelected in ItemState)
5.     and IsDark(Canvas.Pixels[1,1])
6.     and not IsDark(Canvas.Brush.Color) then
7.     begin
8.       if IsDark(clWindowText) then
9.         Canvas.Font.Color := clHighLightText
10.       else
11.         Canvas.Font.Color := clWindowText;
12.     end;
13.     {\$ENDIF}

is about as close as you can get with necessary conditions, but testing any and all linux brands seems almost impossible, even if you add

Code: Pascal  [Select][+][-]
1.     and (GetDefaultLCLWidgetType = lpGtk2)

which would require LCLPlatFormDef in uses of StdCtrls. Anyone with any additional help or ideas...?
Lazarus 2.0.10, FPC 3.2.0, .deb install on Ubuntu Xenial 32 / Gtk2 / Unity7

lucamar

• Hero Member
• Posts: 4219
Re: 'Black on black' combobox items with certain *nix desktops
« Reply #11 on: December 28, 2020, 01:17:28 am »
Theme drawing in Linux is a mix-match of several layers heavily dependent on the system (or rather, the desktop) configuration: there is the base X layout, over it, the Desktop Engine and base toolkit (qt, gtk, etc.), the Window Manager, the Composition manager and, somewhere in between, the Theme engine,  which can modify nilly-willy almost any sub-set of capabilities of the layers below it.

In these conditions it's almost inevitable that sometimes, for apparently no-reason at all, the theming of some group of applications (those compiled with the same or similar widget libraries) fails to reflect some end-user selections (even default ones).

There's little one can do about it other than trying to recognize the situation and "correct it" somehow, as shown in the previous posts. Or expanding (bloating) your "widget" libs to cope with it. Or going your own way and ignoring the system (like wxWidgets does). Or ...

Such is life in the Savage Wildlands of Linux, home of the Free and Open Sources.
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!)
Lazarus/FPC 2.0.8/3.0.4 & 2.0.12/3.2.0 - 32/64 bits on:
(K|L|X)Ubuntu 12..18, Windows XP, 7, 10 and various DOSes.