Project

General

Profile

Feature #1811

implement the AJAX client driver

Added by Greg Shah over 11 years ago. Updated over 7 years ago.

Status:
Closed
Priority:
Normal
Assignee:
-
Start date:
11/01/2013
Due date:
03/06/2014
% Done:

100%

Estimated time:
(Total: 308.00 h)
billable:
No
vendor_id:
GCD

mag_upd20131107a.zip (10.2 KB) Greg Shah, 11/11/2013 03:57 PM

mag_upd20131108a.zip (13 KB) Greg Shah, 11/11/2013 04:00 PM

mag_upd20131111a.zip (9.09 KB) Greg Shah, 11/11/2013 04:02 PM

mag_upd20131112a.zip (18.9 KB) Marius Gligor, 11/12/2013 12:09 PM

mag_upd20131112b.zip (18.8 KB) Marius Gligor, 11/12/2013 01:21 PM

mag_upd20131113a.zip (22.7 KB) Marius Gligor, 11/13/2013 10:06 AM

mag_upd20131114a.zip (52.8 KB) Marius Gligor, 11/14/2013 10:23 AM

problem_loading_page.png (41.1 KB) Marius Gligor, 11/14/2013 10:23 AM

problem_loading_page1.png (55.7 KB) Marius Gligor, 11/14/2013 10:58 AM

mag_upd20131114b.zip (54 KB) Marius Gligor, 11/14/2013 12:20 PM

mag_upd20131116a.zip (62.7 KB) Marius Gligor, 11/18/2013 11:11 AM

mag_upd20131118a.zip (11.9 KB) Marius Gligor, 11/18/2013 01:14 PM

mag_upd20131120a.zip (19 KB) Marius Gligor, 11/20/2013 10:10 AM

spawn.c Magnifier (3.42 KB) Marius Gligor, 11/21/2013 01:22 PM

spawn.c Magnifier (4.15 KB) Marius Gligor, 11/22/2013 11:38 AM

mag_upd20131127a.zip (3.46 KB) Marius Gligor, 11/27/2013 01:08 PM

mag_upd20131127b.zip (3.48 KB) Marius Gligor, 11/27/2013 01:18 PM

mag_upd20131128a.zip (3.5 KB) Marius Gligor, 11/28/2013 05:28 AM

mag_upd20131129a.zip (49.4 KB) Marius Gligor, 11/29/2013 10:28 AM

mag_upd20131202a.zip (49.5 KB) Marius Gligor, 12/02/2013 03:50 AM

mag_upd20131202b.zip (58.6 KB) Marius Gligor, 12/02/2013 12:39 PM

winspawn.zip (13.5 KB) Marius Gligor, 12/02/2013 01:04 PM

build.xml Magnifier (34 KB) Greg Shah, 12/02/2013 08:23 PM

makefile (5.22 KB) Greg Shah, 12/02/2013 08:23 PM

mag_upd20131205a.zip (64.7 KB) Marius Gligor, 12/05/2013 12:18 PM

mag_upd20131206a.zip (65.1 KB) Marius Gligor, 12/06/2013 05:18 AM

mag_upd20131206b.zip (68.1 KB) Marius Gligor, 12/06/2013 12:44 PM

mag_upd20131209a.zip (68.4 KB) Marius Gligor, 12/09/2013 09:26 AM

netbeans-7.4.desktop (265 Bytes) Marius Gligor, 12/10/2013 11:10 AM

mag_upd20131213a.zip (72 KB) Marius Gligor, 12/13/2013 11:20 AM

mag_upd20131216a.zip (74.1 KB) Marius Gligor, 12/16/2013 11:17 AM

mag_upd20131216b.zip (74.8 KB) Marius Gligor, 12/16/2013 01:24 PM

mag_upd20131217a.zip (75.1 KB) Marius Gligor, 12/17/2013 06:46 AM

mag_upd20131218a.zip (78.5 KB) Marius Gligor, 12/18/2013 12:20 PM

mag_upd20131218b.zip (78.3 KB) Marius Gligor, 12/18/2013 01:22 PM

mag_upd20131219a.zip (147 KB) Marius Gligor, 12/19/2013 08:07 AM

mag_upd20131219b.zip (147 KB) Marius Gligor, 12/19/2013 01:03 PM

mag_upd20131220a.zip (148 KB) Marius Gligor, 12/20/2013 01:48 PM

majic_1811.txt Magnifier (9.38 KB) Marius Gligor, 12/23/2013 08:36 AM

mag_upd20131223a.zip (148 KB) Marius Gligor, 12/23/2013 08:36 AM

mag_upd20140107a.zip (148 KB) Marius Gligor, 01/07/2014 11:42 AM

mag_upd20140110a.zip (28.4 KB) Marius Gligor, 01/10/2014 01:15 PM

mag_upd20140113a.zip (31.5 KB) Marius Gligor, 01/13/2014 12:28 PM

protocol.txt Magnifier (2.66 KB) Marius Gligor, 01/13/2014 12:33 PM

tty.png - screen capture ow web tty simulator (37.9 KB) Marius Gligor, 01/14/2014 12:07 PM

mag_upd20140114a.zip (44.1 KB) Marius Gligor, 01/14/2014 12:07 PM

primes.png (43.1 KB) Marius Gligor, 01/15/2014 06:57 AM

conversion.txt Magnifier (23.5 KB) Marius Gligor, 01/15/2014 07:15 AM

client_mag_6832.log Magnifier (4.63 KB) Marius Gligor, 01/15/2014 09:03 AM

server.log Magnifier (57.5 KB) Marius Gligor, 01/15/2014 09:03 AM

primes.zip (228 KB) Marius Gligor, 01/15/2014 10:54 AM

mag_upd20140115a.zip (52.7 KB) Marius Gligor, 01/15/2014 12:32 PM

monospaced.png (5.26 KB) Marius Gligor, 01/16/2014 01:36 PM

chromium.png (33.1 KB) Marius Gligor, 01/16/2014 01:36 PM

mag_upd20140116a.zip (54.1 KB) Marius Gligor, 01/16/2014 01:36 PM

mag_upd20140117a.zip (53.7 KB) Marius Gligor, 01/17/2014 12:10 PM

mag_upd20140117b.zip (53.7 KB) Marius Gligor, 01/17/2014 01:29 PM

mag_upd20140121a.zip (54.4 KB) Marius Gligor, 01/21/2014 10:31 AM

mag_upd20140121b.zip (54.6 KB) Marius Gligor, 01/22/2014 02:37 AM

mag_upd20140122a.zip - JS Dojo code (54.6 KB) Marius Gligor, 01/22/2014 07:51 AM

mag_upd20140122b.zip - JS native code (54.8 KB) Marius Gligor, 01/22/2014 07:51 AM

keyevents.zip (57.2 KB) Marius Gligor, 01/23/2014 12:01 PM

mag_upd20140123a.zip (54.7 KB) Marius Gligor, 01/23/2014 12:01 PM

mag_upd20140124a.zip - Dojo (55.2 KB) Marius Gligor, 01/24/2014 10:40 AM

mag_upd20140124b.zip - Native (55.2 KB) Marius Gligor, 01/24/2014 10:40 AM

mag_upd20140124c.zip (55.2 KB) Marius Gligor, 01/24/2014 01:48 PM

mag_upd20140125a.zip (51.8 KB) Marius Gligor, 01/25/2014 06:31 AM

mag_upd20140125b.zip (51.8 KB) Marius Gligor, 01/25/2014 09:46 AM

mag_upd20140125c.zip (55.2 KB) Marius Gligor, 01/25/2014 10:43 AM

mag_upd20140127a.zip (60 KB) Marius Gligor, 01/27/2014 08:30 AM

mag_upd20140128a.zip (78.3 KB) Marius Gligor, 01/28/2014 11:44 AM

mag_upd20140128b.zip (81.7 KB) Marius Gligor, 01/28/2014 01:13 PM

mag_upd20140128c.zip (81.7 KB) Marius Gligor, 01/28/2014 01:47 PM

mag_upd20140129a.zip (2.44 MB) Marius Gligor, 01/29/2014 02:05 PM

ctrl-c.zip (313 KB) Marius Gligor, 01/30/2014 11:08 AM

mag_upd20140131a.zip (23.1 KB) Marius Gligor, 01/31/2014 03:07 AM

artifacts.png (5.44 KB) Constantin Asofiei, 01/31/2014 05:33 AM

mag_upd20140131b.zip (23 KB) Marius Gligor, 01/31/2014 06:45 AM

menu.png (20.2 KB) Constantin Asofiei, 01/31/2014 07:40 AM

mag_upd20140131c.zip (26.7 KB) Marius Gligor, 01/31/2014 08:28 AM

mag_upd20140203a.zip (25.2 KB) Marius Gligor, 02/03/2014 09:45 AM

mag_upd20140203b.zip (2.68 KB) Marius Gligor, 02/03/2014 12:05 PM

menu.p_ok.png (56.9 KB) Marius Gligor, 02/03/2014 12:05 PM

mag_upd20140204a.zip (18.2 KB) Marius Gligor, 02/04/2014 09:40 AM

mag_upd20140205a.zip (22.7 KB) Marius Gligor, 02/05/2014 10:29 AM

mag_upd20140206a.zip (38.5 KB) Marius Gligor, 02/06/2014 11:45 AM

client_mag_18704.log Magnifier (240 Bytes) Marius Gligor, 02/07/2014 12:13 PM

color.txt Magnifier (3.75 KB) Marius Gligor, 02/10/2014 10:15 AM

chui_swing_color.zip (46.3 KB) Marius Gligor, 02/11/2014 11:56 AM

mag_upd20140211a.zip (11 KB) Marius Gligor, 02/11/2014 11:56 AM

mag_upd20140212a.zip (23.4 KB) Marius Gligor, 02/12/2014 12:42 PM

chui_web_color.zip (178 KB) Marius Gligor, 02/12/2014 12:42 PM

mag_upd20140213a.zip (25.5 KB) Marius Gligor, 02/13/2014 09:06 AM

client_mag_1392289900215.log Magnifier (1.29 KB) Marius Gligor, 02/13/2014 09:06 AM

mag_upd20140213b.zip (25.6 KB) Marius Gligor, 02/13/2014 11:22 AM

mag_upd20140213c.zip (25.6 KB) Marius Gligor, 02/14/2014 11:49 AM

mag_upd20140215a.zip (2.3 MB) Marius Gligor, 02/15/2014 01:55 AM

mag_upd20140217a.zip (2.34 MB) Marius Gligor, 02/17/2014 11:03 AM

mag_upd20140217b.zip (2.34 MB) Marius Gligor, 02/17/2014 01:18 PM

mag_upd20140219c.zip (2.35 MB) Marius Gligor, 02/19/2014 12:59 PM

mag_upd20140220a.zip (25.1 KB) Marius Gligor, 02/20/2014 09:54 AM

mag_upd20140221a.zip (26 KB) Marius Gligor, 02/21/2014 12:00 PM

mag_upd20140221b.zip (25.6 KB) Marius Gligor, 02/21/2014 02:26 PM

mag_upd20140224a.zip (26 KB) Marius Gligor, 02/24/2014 08:25 AM

font.html Magnifier (3.34 KB) Marius Gligor, 02/24/2014 08:25 AM

mag_upd20140225a.zip (19.5 KB) Marius Gligor, 02/25/2014 03:38 AM

server_20140225.log Magnifier (46.9 KB) Marius Gligor, 02/25/2014 12:44 PM

mag_upd20140225b.zip (23.1 KB) Marius Gligor, 02/25/2014 01:43 PM

client_logs_20140225.zip (970 Bytes) Marius Gligor, 02/25/2014 01:43 PM

mag_upd20140226a.zip (23.2 KB) Marius Gligor, 02/26/2014 06:17 AM

test_batch_stdout_no_redirect.zip (5.18 KB) Marius Gligor, 02/26/2014 06:17 AM

test_batch_stdout_redirect.zip (5.57 KB) Marius Gligor, 02/26/2014 06:17 AM

mag_upd20140226c.zip (24.2 KB) Marius Gligor, 02/26/2014 01:01 PM

mag_upd20140227a.zip (26.4 KB) Marius Gligor, 02/27/2014 09:02 AM

mag_upd20140226b.zip (23.2 KB) Marius Gligor, 02/27/2014 10:15 AM

mag_upd20140228a.zip (34 KB) Marius Gligor, 02/28/2014 11:16 AM

mag_upd20140228b.zip (34 KB) Marius Gligor, 02/28/2014 01:10 PM

mag_upd20140303a.zip (41.5 KB) Marius Gligor, 03/03/2014 11:51 AM

mag_upd20140305a.zip (45.6 KB) Marius Gligor, 03/05/2014 07:15 AM

mag_upd20140305b.zip (44 KB) Marius Gligor, 03/05/2014 12:56 PM

mag_upd20140306a.zip (44 KB) Marius Gligor, 03/06/2014 08:48 AM

mag_upd20140306b.zip (44 KB) Marius Gligor, 03/07/2014 04:10 AM

mag_upd20140310a.zip (85.3 KB) Marius Gligor, 03/10/2014 12:10 PM

mag_upd20140311a.zip (85.5 KB) Marius Gligor, 03/11/2014 05:18 AM

mag_upd20140311b.zip (85.4 KB) Marius Gligor, 03/11/2014 12:57 PM

mag_upd20140312a.zip (81.6 KB) Marius Gligor, 03/12/2014 03:24 AM

mag_upd20140312b.zip (81.9 KB) Marius Gligor, 03/12/2014 09:13 AM

mag_upd20140312c.zip (82.9 KB) Marius Gligor, 03/12/2014 12:04 PM

ansi.c Magnifier (71.8 KB) Marius Gligor, 03/17/2014 11:11 AM

mag_upd20140324a.zip (30.6 KB) Marius Gligor, 03/24/2014 12:33 PM

mag_upd20140324b.zip (30.6 KB) Marius Gligor, 03/24/2014 01:21 PM

mag_upd20140325a.zip (30.8 KB) Marius Gligor, 03/25/2014 06:02 AM

mag_upd20140326a.zip (49.4 KB) Marius Gligor, 03/26/2014 02:29 PM

mag_upd20140328a.zip (54.4 KB) Marius Gligor, 03/28/2014 01:50 PM

mag_upd20140402a.zip (65.7 KB) Marius Gligor, 04/02/2014 01:20 PM

mag_upd20140417a.zip (65.8 KB) Marius Gligor, 04/17/2014 07:53 AM

getting_started_with_p2j_development.html Magnifier (14.6 KB) Marius Gligor, 04/22/2014 01:01 PM

mag_upd20140424a.zip (63.3 KB) Marius Gligor, 04/24/2014 10:46 AM

vt100_test1.png (19 KB) Marius Gligor, 04/24/2014 10:46 AM

mag_upd20140425a.zip (64.6 KB) Marius Gligor, 04/25/2014 09:51 AM

mag_upd20140428a.zip (65.7 KB) Marius Gligor, 04/28/2014 01:14 PM

mag_upd20140429a.zip (65.8 KB) Marius Gligor, 04/29/2014 11:27 AM

mag_upd20140429b.zip (65.9 KB) Marius Gligor, 04/29/2014 11:48 AM

mag_upd20140429c.zip (66 KB) Marius Gligor, 04/29/2014 01:14 PM

mag_upd20140507a.zip (9.11 KB) Marius Gligor, 05/07/2014 05:46 AM

swing-process.png (10.3 KB) Constantin Asofiei, 05/07/2014 06:19 AM

mag_upd20140507b.zip (11.8 KB) Marius Gligor, 05/07/2014 06:57 AM

mag_upd20140507c.zip (11.9 KB) Marius Gligor, 05/07/2014 09:19 AM

mag_upd20140508a.zip (18.4 KB) Marius Gligor, 05/08/2014 01:47 AM

mag_upd20140508b.zip (23 KB) Marius Gligor, 05/08/2014 03:46 AM

mag_upd20140702a.zip (18.6 KB) Marius Gligor, 07/02/2014 12:05 PM

mag_upd20140703a.zip (19 KB) Marius Gligor, 07/03/2014 08:16 AM

paste_example.html Magnifier (1.81 KB) Marius Gligor, 07/04/2014 09:26 AM

copy_paste_example.html Magnifier (1.78 KB) Marius Gligor, 07/08/2014 12:51 PM

dialog.html Magnifier (633 Bytes) Marius Gligor, 07/08/2014 01:06 PM

mag_upd20140709a.zip (22.1 KB) Marius Gligor, 07/09/2014 11:46 AM

mag_upd20140711a.zip (22.3 KB) Marius Gligor, 07/11/2014 03:57 AM

mag_upd20140714a.zip (28.9 KB) Marius Gligor, 07/14/2014 05:05 AM

mag_upd20140714b.zip (28.9 KB) Marius Gligor, 07/14/2014 01:34 PM

ca_upd20141206d.zip (13.8 KB) Constantin Asofiei, 12/06/2014 03:28 PM

ges_upd20150507a.zip (579 KB) Greg Shah, 05/07/2015 06:48 PM

compare.sh Magnifier (22.2 KB) Greg Shah, 05/07/2015 06:48 PM

ges_upd20150508a.zip (580 KB) Greg Shah, 05/08/2015 09:21 AM

compare.sh Magnifier (22.4 KB) Greg Shah, 05/08/2015 10:05 AM

ges_upd20150508b.zip (582 KB) Greg Shah, 05/08/2015 10:05 AM

ges_upd20150508c.zip (582 KB) Greg Shah, 05/08/2015 10:52 AM

ges_upd20150508d.zip (594 KB) Greg Shah, 05/09/2015 12:06 AM

compare.sh Magnifier (22.8 KB) Greg Shah, 05/09/2015 12:09 AM

ges_upd20150509a.zip (594 KB) Greg Shah, 05/09/2015 07:03 AM

ges_upd20150515a.zip (596 KB) Greg Shah, 05/15/2015 12:22 PM

ges_upd20150515b.zip (596 KB) Greg Shah, 05/15/2015 02:30 PM

ab_upd20150516a.zip (12.4 KB) Anton Breaur, 05/16/2015 09:20 AM

ges_upd20150516a.zip (599 KB) Greg Shah, 05/16/2015 10:29 AM

compare.sh Magnifier - Use this to code review the update. (23 KB) Greg Shah, 05/16/2015 11:35 AM

rem.sh Magnifier - This deletes everything needed from a p2j directory so that the update can be applied. (3.17 KB) Greg Shah, 05/16/2015 11:35 AM

ca_upd20150516a.zip (4.97 KB) Constantin Asofiei, 05/16/2015 01:57 PM

ges_upd20150517a.zip (599 KB) Greg Shah, 05/17/2015 11:07 AM

ges_upd20150518a.zip (594 KB) Greg Shah, 05/18/2015 12:10 PM

compare.sh Magnifier (22.8 KB) Greg Shah, 05/18/2015 12:10 PM

ab_upd20150518a.zip (7.72 KB) Anton Breaur, 05/18/2015 02:01 PM

compare.sh Magnifier (339 Bytes) Anton Breaur, 05/18/2015 02:01 PM

ges_upd20150518b.zip (594 KB) Greg Shah, 05/18/2015 04:21 PM

fonts_js.zip - partial demo work for javascript string drawing (244 KB) Anton Breaur, 05/19/2015 07:06 PM

fonts_js.zip - Here is a bundle with the new approach (56.3 KB) Anton Breaur, 05/22/2015 07:54 AM

fonts_js.zip (57.6 KB) Anton Breaur, 05/25/2015 03:28 PM

copy_paste_example_20150713.html Magnifier (3.6 KB) Greg Shah, 07/13/2015 05:20 PM

ParentChildFrames.java Magnifier (1.26 KB) Greg Shah, 07/14/2015 02:37 PM

canvas_drawing_example_20150721.html Magnifier (4.87 KB) Greg Shah, 07/21/2015 06:20 PM

canvas_drawing_output_comparison.jpg (89.6 KB) Greg Shah, 07/21/2015 06:20 PM

GraphicsOpsExamples.java Magnifier (1.72 KB) Greg Shah, 07/21/2015 06:20 PM

WhyUseStrictDirective.pdf - "JavaScript’s Strict Mode and Why You Should Use It" http://cjihrig.com/blog/javascripts-strict-mode-and-why-you-should-use-it/ gives analysis of why "use strict" is safe to do. (80.1 KB) Sergey Ivanovskiy, 07/23/2015 02:26 PM

canvas_drawing_example_20150723.html Magnifier (13.2 KB) Greg Shah, 07/23/2015 06:08 PM

canvas_drawing_output_comparison_20150723.jpg (126 KB) Greg Shah, 07/23/2015 06:08 PM

GraphicsOpsExamples.java Magnifier (2.48 KB) Greg Shah, 07/23/2015 06:09 PM

example_of_javascript_canvas_anti_aliasing_effects_compared_to_java2d.png (7.69 KB) Greg Shah, 07/24/2015 10:01 AM

canvas_drawing_output_comparison_offset_tick_marks_20150724a.jpg (90.4 KB) Greg Shah, 07/24/2015 10:01 AM

canvas_drawing_output_comparison_tick_marks_with_translate_by_0.5_20150724b.jpg (89.7 KB) Greg Shah, 07/24/2015 10:01 AM

example_of_javascript_canvas_with_translate_0.5_anti_aliasing_effects_compared_to_java2d.png (8.52 KB) Greg Shah, 07/24/2015 10:01 AM

canvas_drawing_output_comparison_manual_pixel_overwriting_20150724.jpg (157 KB) Greg Shah, 07/24/2015 06:31 PM

canvas_drawing_example_20150724.html Magnifier (17.8 KB) Greg Shah, 07/24/2015 06:31 PM

1811p_diff.txt Magnifier (8.97 KB) Sergey Ivanovskiy, 07/27/2015 10:30 AM

canvas_drawing_example_20150727.html Magnifier (19.6 KB) Greg Shah, 07/27/2015 05:35 PM

canvas_drawing_output_comparison_20150727.jpg (161 KB) Greg Shah, 07/27/2015 05:35 PM

1811p_all_changes.txt Magnifier (15.5 KB) Sergey Ivanovskiy, 07/31/2015 08:29 AM

1811p_all_changes_1.txt Magnifier (16.3 KB) Sergey Ivanovskiy, 07/31/2015 01:54 PM

test.zip (37.7 KB) Sergey Ivanovskiy, 08/04/2015 06:03 PM

test_20150805.zip (40.6 KB) Sergey Ivanovskiy, 08/05/2015 12:54 PM

test_20150806.zip (22.2 KB) Sergey Ivanovskiy, 08/06/2015 05:33 PM

1811o_20150807_1.txt Magnifier (23.1 KB) Sergey Ivanovskiy, 08/07/2015 12:20 PM

1811o_20150807_2.txt Magnifier (21.9 KB) Sergey Ivanovskiy, 08/07/2015 04:52 PM

1811o_20150810_1.txt Magnifier (56.6 KB) Sergey Ivanovskiy, 08/10/2015 05:01 PM

1811o_web_chui_client.txt Magnifier (2.05 KB) Sergey Ivanovskiy, 08/11/2015 08:11 AM

1811o_fix_web_chui_client_1.txt Magnifier (1.77 KB) Sergey Ivanovskiy, 08/11/2015 10:29 AM

1811o_20150811_1.txt Magnifier (9.11 KB) Sergey Ivanovskiy, 08/11/2015 06:17 PM

1811o_20150812.txt Magnifier (2.9 KB) Sergey Ivanovskiy, 08/12/2015 12:40 PM

canvas_resize_copy_bits_test_20150812.html Magnifier (3.4 KB) Greg Shah, 08/12/2015 01:38 PM

clipboard_basic.p Magnifier (459 Bytes) Sergey Ivanovskiy, 08/13/2015 11:00 AM

simple_sm.png (14.7 KB) Sergey Ivanovskiy, 08/13/2015 11:36 AM

combo_box.combo_box9_1.p.png (15.5 KB) Sergey Ivanovskiy, 08/13/2015 12:22 PM

rectangle.rect_test2.p.png (14.2 KB) Sergey Ivanovskiy, 08/13/2015 12:22 PM

rectangle.rect_test2.p_done.png (14.2 KB) Sergey Ivanovskiy, 08/13/2015 12:22 PM

rectangle.rect_test6.p.png (13.7 KB) Sergey Ivanovskiy, 08/13/2015 12:22 PM

rectangle.rect_test7_1.p.png (13.1 KB) Sergey Ivanovskiy, 08/13/2015 12:22 PM

simple_alert_box.p.png (13.2 KB) Sergey Ivanovskiy, 08/13/2015 12:22 PM

toggle_box.gui.tbx_present.p.png (14.7 KB) Sergey Ivanovskiy, 08/13/2015 12:22 PM

toggle_box.gui.tbx_present.p_done.png (14.8 KB) Sergey Ivanovskiy, 08/13/2015 12:22 PM

tests_suite_conversion_trunc.txt Magnifier (66.5 KB) Sergey Ivanovskiy, 08/13/2015 12:51 PM

test_list (280 Bytes) Sergey Ivanovskiy, 08/13/2015 12:51 PM

font_face_load.zip (2.29 MB) Constantin Asofiei, 08/13/2015 01:09 PM

1811o_20150813_1.txt Magnifier (10.2 KB) Sergey Ivanovskiy, 08/13/2015 04:37 PM

1811q_20150814.txt Magnifier (10.2 KB) Sergey Ivanovskiy, 08/14/2015 08:41 AM

canvas_resize_move_zorder_test_20150814.html Magnifier (4.59 KB) Greg Shah, 08/14/2015 02:39 PM

canvas_resize_move_zorder_test_20150814.png (7.58 KB) Greg Shah, 08/14/2015 02:39 PM

paragraph_drawing.zip (719 KB) Constantin Asofiei, 08/15/2015 05:14 PM

p2j.strokes.js (4.57 KB) Sergey Ivanovskiy, 08/17/2015 10:22 AM

LineStrokeTest_20150818.zip (5.13 KB) Sergey Ivanovskiy, 08/18/2015 09:02 AM

LineStrokeTest_20150818.zip (6.11 KB) Sergey Ivanovskiy, 08/18/2015 12:49 PM

J2DWithStrokes.png (31.5 KB) Sergey Ivanovskiy, 08/18/2015 09:10 PM

BrowserWithStrokesNativeImpl.png (51.8 KB) Sergey Ivanovskiy, 08/18/2015 09:10 PM

BrowserWithStrokesCustomImpl.png (35.4 KB) Sergey Ivanovskiy, 08/18/2015 09:10 PM

GraphicsOpsExamples2.java Magnifier (5.07 KB) Sergey Ivanovskiy, 08/18/2015 09:10 PM

LineStrokeTest_20150819.zip (8.4 KB) Sergey Ivanovskiy, 08/19/2015 09:33 AM

BrowserWithStrokesCustomImpl.png (36.5 KB) Sergey Ivanovskiy, 08/19/2015 09:33 AM

BrowserWithStrokesNativeImpl.png (48.3 KB) Sergey Ivanovskiy, 08/19/2015 09:33 AM

J2DWithStrokes.png (31.5 KB) Sergey Ivanovskiy, 08/19/2015 09:33 AM

LineStrokeTest_20150819_2.zip (8.36 KB) Sergey Ivanovskiy, 08/19/2015 03:01 PM

1811q_strokes_2.txt Magnifier (39.4 KB) Sergey Ivanovskiy, 08/20/2015 10:42 AM

gui_web_client_first_almost_working_attempt_20150820.png (23.9 KB) Greg Shah, 08/20/2015 05:22 PM

better_drawing.png (18 KB) Constantin Asofiei, 08/21/2015 06:16 AM

save_translated_origin.zip (16.3 KB) Constantin Asofiei, 08/21/2015 06:17 AM

better_drawing_polygons.png (7.3 KB) Greg Shah, 08/21/2015 08:14 AM

hello_world_swing.png (6.73 KB) Greg Shah, 08/21/2015 08:25 AM

FixToResizeImages.png (11.7 KB) Sergey Ivanovskiy, 08/21/2015 11:53 AM

fix_to_resize_images_1.txt Magnifier (1.42 KB) Sergey Ivanovskiy, 08/21/2015 11:53 AM

overdrawing_in_the_gui_client.png (6.56 KB) Greg Shah, 08/21/2015 01:06 PM

colors_interpolations_1.txt Magnifier (2 KB) Sergey Ivanovskiy, 08/21/2015 04:22 PM

colors_interpolations_2.txt Magnifier (4.8 KB) Sergey Ivanovskiy, 08/22/2015 03:55 PM

Int32ReadWriteTest.js (4.95 KB) Sergey Ivanovskiy, 08/22/2015 04:14 PM

1811q_strokes_4.txt Magnifier (6.4 KB) Sergey Ivanovskiy, 08/24/2015 03:04 PM

hello_world_web_gui_vs_swing_gui_20150825.png (11.5 KB) Greg Shah, 08/25/2015 02:30 PM

log4jsConsole.png (227 KB) Sergey Ivanovskiy, 08/25/2015 03:49 PM

log4javascript_diff_1.txt Magnifier (3.46 KB) Sergey Ivanovskiy, 08/25/2015 04:08 PM

canvas_drawing_example_20150826.html Magnifier (19.3 KB) Greg Shah, 08/26/2015 09:21 AM

canvas_drawing_output_comparison_blue_20150826.jpg (161 KB) Greg Shah, 08/26/2015 09:21 AM

GraphicsOpsExamples.java Magnifier (4.81 KB) Greg Shah, 08/26/2015 09:21 AM

hello_world_web_gui_vs_swing_gui_20150826a.png (11.5 KB) Greg Shah, 08/26/2015 09:21 AM

web_gui_js.log Magnifier (19.8 KB) Greg Shah, 08/26/2015 01:14 PM

web_gui_java_stderr.log Magnifier (23 KB) Greg Shah, 08/26/2015 01:14 PM

swing_gui_java_stderr.log Magnifier (26.9 KB) Greg Shah, 08/26/2015 01:14 PM

p2j.screen_diff_1.txt Magnifier (7.81 KB) Sergey Ivanovskiy, 08/26/2015 05:39 PM

hello_world_web_gui_vs_swing_gui_20150826b.png (11.4 KB) Greg Shah, 08/26/2015 06:47 PM

canvas_drawing_example_20150826.html Magnifier (19.6 KB) Greg Shah, 08/26/2015 06:47 PM

p2j.screen_diff_2.txt Magnifier (8.47 KB) Sergey Ivanovskiy, 08/26/2015 07:29 PM

webClient.png (18.5 KB) Sergey Ivanovskiy, 08/26/2015 07:29 PM

1811q_diff_20150827.txt Magnifier (8.48 KB) Sergey Ivanovskiy, 08/27/2015 03:56 AM

1811q_diff_20150827_2.txt Magnifier (12.9 KB) Sergey Ivanovskiy, 08/27/2015 09:58 AM

javascript_test_of_96_dpi_assumptions.png (5.9 KB) Greg Shah, 08/27/2015 01:18 PM

java2d_test_of_96_dpi_assumptions.png (8.87 KB) Greg Shah, 08/27/2015 01:18 PM

canvas_w_96px_square.html Magnifier (3.2 KB) Greg Shah, 08/27/2015 01:19 PM

FontSizeExample.java Magnifier (1.59 KB) Greg Shah, 08/27/2015 01:19 PM

hello_world_web_gui_vs_swing_gui_20150827a.png (11.1 KB) Greg Shah, 08/27/2015 03:00 PM

close_button_corruption.png (1.48 KB) Greg Shah, 08/27/2015 03:00 PM

message_area_sticks_out_1_px_in_swing.png (3.27 KB) Greg Shah, 08/27/2015 03:00 PM

fonts_web_vs_swing.png (16.4 KB) Constantin Asofiei, 08/28/2015 09:20 AM

drawCloseButton.js (1.63 KB) Sergey Ivanovskiy, 08/28/2015 10:35 AM

GraphicsOpsExamples2.java Magnifier (9.79 KB) Sergey Ivanovskiy, 08/28/2015 03:55 PM

strokesJava2DTest.png (17 KB) Sergey Ivanovskiy, 08/28/2015 03:55 PM

strokesJava2DTest_antialias_on_off.png (19.1 KB) Sergey Ivanovskiy, 08/29/2015 04:10 PM

GraphicsOpsExamples2.java Magnifier (11.7 KB) Sergey Ivanovskiy, 08/29/2015 04:10 PM

caption_buttons.png (721 Bytes) Hynek Cihlar, 08/31/2015 12:13 PM

GraphicsOpsExamples2.java Magnifier (13.1 KB) Sergey Ivanovskiy, 08/31/2015 01:08 PM

CaptionButton_diff.txt Magnifier (1.25 KB) Sergey Ivanovskiy, 08/31/2015 01:08 PM

StrokeControl&LineCoveredWithSquares.png (20.3 KB) Sergey Ivanovskiy, 08/31/2015 01:19 PM

gui&webClients.png (24.4 KB) Sergey Ivanovskiy, 08/31/2015 05:28 PM

1811q_diff_20150901.txt Magnifier (13.9 KB) Sergey Ivanovskiy, 09/01/2015 08:50 AM

1811q_diff_20150901_2.txt Magnifier (10.6 KB) Sergey Ivanovskiy, 09/01/2015 02:39 PM

1811q_diff_20150901_3.txt Magnifier (7.83 KB) Sergey Ivanovskiy, 09/01/2015 04:18 PM

1811q_diff_20150901_4.txt Magnifier (3.32 KB) Sergey Ivanovskiy, 09/01/2015 04:58 PM

SetImageIconTest.java Magnifier (2.4 KB) Sergey Ivanovskiy, 09/02/2015 02:19 PM

SetImageIconTest.java Magnifier (2.84 KB) Sergey Ivanovskiy, 09/02/2015 03:43 PM

SetImageIconTest.java Magnifier (2.26 KB) Sergey Ivanovskiy, 09/03/2015 07:32 AM

1811q_20150906_1.txt Magnifier (24.8 KB) Sergey Ivanovskiy, 09/06/2015 07:51 PM

1811q_20150907_2.txt Magnifier (3.51 KB) Sergey Ivanovskiy, 09/07/2015 05:31 PM

1811q_201509008_1.txt Magnifier (12 KB) Sergey Ivanovskiy, 09/08/2015 06:21 AM

1811q_201509008_2.txt Magnifier (17.5 KB) Sergey Ivanovskiy, 09/08/2015 01:09 PM

1811q_set_title.txt Magnifier (2.33 KB) Sergey Ivanovskiy, 09/08/2015 06:04 PM

1811q_20150909_1.txt Magnifier (22.4 KB) Sergey Ivanovskiy, 09/09/2015 11:59 AM

TaskBarHorizontal.png (52.6 KB) Sergey Ivanovskiy, 09/09/2015 12:14 PM

taskBarVertical.png (61.8 KB) Sergey Ivanovskiy, 09/09/2015 12:14 PM

1811q_20150909_2.txt Magnifier (14.8 KB) Sergey Ivanovskiy, 09/09/2015 06:04 PM

window1_visible.png (28.2 KB) Sergey Ivanovskiy, 09/10/2015 05:25 AM

window1_hidden.png (25.5 KB) Sergey Ivanovskiy, 09/10/2015 05:25 AM

P2JGUIClient.png (53.5 KB) Sergey Ivanovskiy, 09/10/2015 09:47 AM

VirtualDesktopTest.png (36.6 KB) Sergey Ivanovskiy, 09/10/2015 09:47 AM

TaskBarChangedColours.png (44.8 KB) Sergey Ivanovskiy, 09/10/2015 10:10 AM

VirtualDesktop.zip (24.9 KB) Sergey Ivanovskiy, 09/10/2015 10:10 AM

1811q_20150910_1.txt Magnifier (12.8 KB) Sergey Ivanovskiy, 09/10/2015 12:59 PM

visibility_test.p Magnifier (2.32 KB) Sergey Ivanovskiy, 09/10/2015 03:53 PM

images_artifacts.png (10 KB) Sergey Ivanovskiy, 09/11/2015 08:02 AM

1811q_20150911_1.txt Magnifier (3.64 KB) Sergey Ivanovskiy, 09/11/2015 08:17 AM

combo_box_button_mostly_works.png (20.8 KB) Greg Shah, 09/11/2015 08:53 AM

1811q_20150911_2.txt Magnifier (5.19 KB) Sergey Ivanovskiy, 09/11/2015 03:34 PM

1811q_20150913_1.txt Magnifier (6.82 KB) Sergey Ivanovskiy, 09/13/2015 07:09 PM

1811q_20150914_1.txt Magnifier (2.28 KB) Sergey Ivanovskiy, 09/14/2015 10:07 AM

1811q_20150914_3.txt Magnifier (3.39 KB) Sergey Ivanovskiy, 09/14/2015 02:42 PM

1811q_20150915_1.txt Magnifier (1.14 KB) Sergey Ivanovskiy, 09/15/2015 12:01 PM

logs_MenuGuiImpl_217.txt Magnifier (3.42 KB) Sergey Ivanovskiy, 09/18/2015 11:00 AM

set_window_bounds_diff.txt Magnifier (6.43 KB) Sergey Ivanovskiy, 09/18/2015 01:07 PM

web_client_draw_logs.txt Magnifier (8.07 KB) Sergey Ivanovskiy, 09/18/2015 01:07 PM

web_server_draw_logs.txt Magnifier (156 KB) Sergey Ivanovskiy, 09/18/2015 01:07 PM

hello_world_p2j_swing_broken_20150918.png (7.5 KB) Greg Shah, 09/18/2015 01:36 PM

hello_world_4gl_windows_screen_capture.png (3.66 KB) Greg Shah, 09/18/2015 01:36 PM

keyboards.zip (14.4 KB) Sergey Ivanovskiy, 09/20/2015 04:56 PM

ExceptionListForTheSecondCondition.txt Magnifier (7.08 KB) Sergey Ivanovskiy, 09/21/2015 11:04 AM

editorWidget.p Magnifier (971 Bytes) Sergey Ivanovskiy, 09/22/2015 01:42 PM

keyboards.zip (38.7 KB) Sergey Ivanovskiy, 09/23/2015 05:55 PM

1811q_20150924.txt Magnifier (5.63 KB) Sergey Ivanovskiy, 09/23/2015 07:55 PM

1811q_20150924_2.txt Magnifier (14.6 KB) Sergey Ivanovskiy, 09/24/2015 10:22 AM

test.html Magnifier (11.7 KB) Sergey Ivanovskiy, 09/24/2015 10:22 AM

clip_test2.html Magnifier (822 Bytes) Constantin Asofiei, 09/25/2015 06:02 AM

test.html Magnifier (21.1 KB) Sergey Ivanovskiy, 09/25/2015 02:40 PM

keyboards_20150928.zip (6.76 KB) Sergey Ivanovskiy, 09/28/2015 02:49 PM

dll_build_test_windev01.png (71.9 KB) Sergey Ivanovskiy, 10/02/2015 04:30 PM

couldnt_load_dll_procedure.png (72.7 KB) Sergey Ivanovskiy, 10/02/2015 04:30 PM

Success.png (51.6 KB) Sergey Ivanovskiy, 10/03/2015 07:00 PM

Window_3D_border_fix_20151007.jpg - Window 3D border fix (25.8 KB) Eugenie Lyzenko, 10/07/2015 05:20 PM

status_line_fix_20151008.jpg - Fixed status line painting (16.3 KB) Eugenie Lyzenko, 10/08/2015 02:10 PM

caching_images_20151010.txt Magnifier (15 KB) Sergey Ivanovskiy, 10/10/2015 05:40 AM

caching_images_20151011.txt Magnifier (20.4 KB) Sergey Ivanovskiy, 10/11/2015 05:11 PM

caching_images_20151012.txt Magnifier (22.5 KB) Sergey Ivanovskiy, 10/12/2015 12:44 PM

clipboard_20151014.txt Magnifier (10.6 KB) Sergey Ivanovskiy, 10/13/2015 07:18 PM

clipboard_20151014_2.txt Magnifier (8.34 KB) Sergey Ivanovskiy, 10/14/2015 12:35 PM

demo_widgets.png (14.5 KB) Sergey Ivanovskiy, 10/14/2015 04:52 PM

p2j_poor_title_font_20151014.jpg - New font(not easy to read) (6.1 KB) Eugenie Lyzenko, 10/14/2015 07:58 PM

p2j_good_title_font_20151014.jpg - Old and nice font (6.51 KB) Eugenie Lyzenko, 10/14/2015 07:58 PM

exp_results.txt Magnifier (29.6 KB) Sergey Ivanovskiy, 10/15/2015 10:58 AM

keys_measure_test_diff.txt Magnifier (2.17 KB) Sergey Ivanovskiy, 10/15/2015 11:07 AM

clients_result.txt Magnifier (3.89 KB) Sergey Ivanovskiy, 10/15/2015 12:11 PM

server_result.txt Magnifier (1.65 KB) Sergey Ivanovskiy, 10/15/2015 12:11 PM

keys_measure_java_test_diff.txt Magnifier (5.18 KB) Sergey Ivanovskiy, 10/15/2015 12:15 PM

demo_widget_regression.png (17.8 KB) Sergey Ivanovskiy, 10/15/2015 03:35 PM

demo_widgets.p_p2j_20151015_1811s_v10969.jpg - GUI regression (61.7 KB) Eugenie Lyzenko, 10/15/2015 03:53 PM

putImagData_1.txt Magnifier (14.7 KB) Sergey Ivanovskiy, 10/16/2015 07:44 AM

first_step_1.png (24.5 KB) Sergey Ivanovskiy, 10/16/2015 07:57 AM

select_item_step.png (30.6 KB) Sergey Ivanovskiy, 10/16/2015 07:57 AM

fill_in_foreground_regression_10977.jpg - Regression in 10977 (7.76 KB) Eugenie Lyzenko, 10/16/2015 01:17 PM

mouse_20151019_1.txt Magnifier (16.2 KB) Sergey Ivanovskiy, 10/19/2015 08:41 AM

AskGui.png (24.1 KB) Sergey Ivanovskiy, 10/19/2015 10:38 AM

calc-static-chars.png (19.8 KB) Sergey Ivanovskiy, 10/23/2015 09:38 AM

web-calc-static-chars.png (23 KB) Sergey Ivanovskiy, 10/23/2015 09:38 AM


Subtasks

Feature #2200: enable a Jetty web server to be initialized and used within a client processClosedMarius Gligor

Feature #2201: implement the web client process spawning and server redirectClosedMarius Gligor

Feature #2212: design websockets protocol for bidirectional communication between the javascript presentation engine and the java-based JS UI driver (ChUI and GUI versions)ClosedMarius Gligor

Feature #2238: investigate and resolve issues related to browser "session management"ClosedMarius Gligor

Feature #2240: implement proper color support in the Swing ChUI and AJAX ChUI clientsClosedMarius Gligor

Feature #2247: interactive child process supportClosedMarius Gligor

Feature #2306: add copy/paste support to the web clientClosedMarius Gligor

Feature #2308: remote client launchClosedMarius Gligor

Bug #2929: test chrome browser supportClosedSergey Ivanovskiy

Bug #2930: test IE browser supportClosedSergey Ivanovskiy


Related issues

Related to User Interface - Bug #2244: web client performance Closed 02/17/2014 02/26/2014
Related to Runtime Infrastructure - Feature #2243: integrate spawn.c changes into winspawn.c Closed
Related to User Interface - Feature #2298: temporary account creation New 05/05/2014
Related to User Interface - Feature #2342: fix cross-domain cookie for web client on remote launch Closed 07/22/2014
Related to User Interface - Feature #1807: implement clipboard support Closed
Related to User Interface - Bug #2664: vim doesn't work as an interactive child process in either Swing ChUI or the web ChUI client New 08/28/2015
Related to User Interface - Feature #1794: implement font support Closed
Related to User Interface - Bug #2956: When switching windows with Web GUI task bar the source window is not deactivated Closed
Related to User Interface - Bug #2964: fix key processing for web chui in Firefox, Chrome, IE and Safari New 01/21/2016
Related to User Interface - Bug #2969: websocket keep-alive Closed
Related to User Interface - Bug #2968: disconnect/reconnect and manual reload must re-establish the websocket connection, needs to update the websock references in all GuiWebEmulatedWindows Closed
Related to User Interface - Feature #2967: web socket message sizes are limited to 32K, make sure that drawing ops and images larger than this are handled properly Closed
Related to User Interface - Support #2672: GUI web client performance improvements New
Related to User Interface - Feature #2673: support a kind of virtual desktop in the GUI web client WIP
Related to User Interface - Support #2674: list of HTML5, CSS and Javascript features in use in the web client (both ChUI and GUI) + minimum browser levels New
Related to User Interface - Support #2675: list of differences between the web GUI client and the original 4GL Windows GUI environment New
Related to User Interface - Bug #2931: test Safari browser support Closed
Related to User Interface - Feature #2666: web client login page improvements New
Related to User Interface - Feature #2683: reverse proxy implementation Closed
Related to User Interface - Bug #3250: win_spawn failure cannot create process Closed

History

#1 Updated by Greg Shah over 11 years ago

  • Target version set to Milestone 12

#2 Updated by Greg Shah over 10 years ago

In the following, the term AJAX is used as a shorthand for a javascript implementation that runs as a "web application", however we are planning to use websockets instead of XmlHttpRequest, so the term AJAX is not technically accurate.

The 4GL is inherently a fat-client environment. Many features of the implementation are designed such that the execution of a 4GL process (pro, mpro, prowin32...) assumes that the processing occurs within the context of a single user's account. For example, 4GL code can:

  • launch child processes
  • access the file system, printers and other devices
  • create buffers in memory and directly manipulate that memory
  • make calls to arbitrary shared library (DLL) entry points
  • use sockets

In a perfect world, we would be able to virtualize everything and run it in a single server on threads that maintain the proper user context. That is not possible. Each of these items above (and there are more than those listed) must run in the context of the current OS user account. For example, the security and permissions for child processes and file system access cannot be properly duplicated in a shared server environment. Likewise, when the 4GL client starts a "server socket", it might bind that socket to a specific port. If 4GLclients are started on different systems, this would be perfectly possible but it would not be something we could duplicate in an "everything is virtualized in the server" approach. DLL access is similar, since there can only be 1 copy of a DLL in a process at one time and if a DLL was not written to be thread safe/context aware on a per thread basis, then 2 users accessing a non-safe DLL from the same process could corrupt data and cause abends and worse.

The only correct way to implement this is on the client-side. Each P2J client thus has its own process, running in the context of the OS user account and this exactly replicates the execution environment of the 4GL, allowing us to replace all of these client features. Of course, the majority of the processing (business logic, database access...) is still done in the shared server (on threads with dedicated context for that user), but when something client-specific must execute, we remotely access that functionality on the client. More specifically, we call down to the client, from the server, using our remote object protocol.

This means that each user of the system must have their own process in which they run the client-specific portions of the application. To date, this has always meant a Java process, running our P2J client code.

What does all of this have to do with implementing an AJAX client?

The UI for any of our clients (ChUI or GUI) is by nature, a client-side thing. With the move to an AJAX UI, we will be moving the interactive portions (rendering/display of the UI and processing key/mouse input) of the UI into the browser. This is a core objective of the current project and represents a very interesting deployment scenario for virtually all customers with which we have ever spoken.

However, we don't plan to re-implement our entire client in javascript. For one thing, the P2J client code is too complex to easily port to javascript. Secondly, maintaining 2 implementations of the same client is not something we are interested in doing.

With this in mind, in addition to the "every user is a separate process" requirement, we have these other objectives:

  • Maximize the use of our current Java client code.
  • Maximize the use of common code (in Java) in the future solution.
  • Minimize the code on the Javascript side.

The above constraints suggest this approach:

All processing will be HTTPS.

The browser client will open a URL that is serviced by Jetty in the P2J server process. I'll call this the "server jetty". The URL will be something like https://p2jserverhost:jettyport/chui/ (for the AJAX ChUI) or https://p2jserverhost:jettyport/gui/ (for the AJAX GUI).

When the server jetty has an inbound connection for one of these AJAX URLs, it will do the following:

  1. dynamically start a new P2J client process (we already do something very similar in starting "appserver instances")
  2. this will be done by a special launcher process that is there to create child processes under the control of the P2J server
  3. the new client will be started in "browser mode" (ChUI or GUI) and the client code will be the standard P2J java client
  4. that P2J client will start up and will make an initial connection to the server's secure port using the P2J protocol (this is the "client jetty")
  5. the P2J client will have a Jetty instance in its JVM that is initialized to offer HTTPS support
  6. the P2J client will notify the P2J server about the URL for which the client jetty is listening, this will include a custom/dynamic port number
  7. the server jetty will respond to the original browser request with a page that redirects the browser to the new client jetty URL
  8. the browser will connect to the client jetty, which will send down a page that is the proper AJAX client (ChUI or GUI)
  9. that AJAX client will establish a secure websocket connection to the client jetty
  10. on the P2J client side, the "upper half" of the AJAX driver will be written in Java and it will map into the ChUI driver interface or the GUI driver interface
  11. on the browser side, the javascript client will be the "lower half" of the ChUI driver interface or the GUI driver interface, responsible for the rendering of specific windows, frames, widgets... and handling all user input (keys and mouse)
  12. these two halves of the AJAX driver will cooperatively process over the websocket to provide the same kind of UI services that could have been done with an in-process driver in Swing
  13. the P2J client will handle the majority of the processing and in fact it can be virtually the same as it is today, including all of the session setup/communications with the P2J server
  14. the only difference is that instead of using Swing for the low level drawing and input events, those aspects must be remoted over the websocket
  15. this means that the our current client architecture is used exactly as it is designed today, and it can handle all the same client side features like child processes, file system access, DLL calls and so forth
  16. those features are just implemented outside of the browser, and potentially on a different physical system, although technically it would be possible to have that client process be local to the same system on which the browser is running (this may be needed in some use cases where the client features in use require interaction with the user or some kind of device access that is not available on the server)

#3 Updated by Greg Shah over 10 years ago

For task #2200, the following must be reviewed/considered:

  • The startup process for the ClientDriver and ThinClient will be different with Jetty.
  • The AJAX mode for the client is basically a new driver implementation. That means it is going to be mutually exclusive with running any other driver. For example, in AJAX ChUI mode, it would replace the Swing ChUI client AND the JNI ("Console") ChUI client.
  • The client will listen on a random port number (above 1024) which will allow the port number to be allocated dynamically by the OS/Java and also for there to be no security restrictions on opening that port.
  • The client jetty port will have to be reported back to the server (so that the server jetty can construct the proper redirect page), but that back-channel communication will probably not be handled in #2200. We will have a subsequent task defined to implement the dynamic launch of the AJAX client and that back-channel comms will occur are part of the launch.
  • The client jetty must startup in SSL mode and we need to consider what keys are used. The server jetty uses the SSL context already loaded by the server's SecurityManager, which means it is backed by the keys that are configured in the server's directory. At a minimum, we want to enable the same server-configured keys to be utilized in the client jetty. Such a configuration is attractive because it will either allow a properly signed certificate to be re-used or the user's web browser will already have the necessary "certificate exceptions" loaded. In other words, we want to use SSL without having to do any per-client configuration in the common case.
  • However, we must also enable the cases where there is client SSL configuration (in our directory as the 1st choice and then possibly overridden through local files/bootstrap config). For example, if the clients are running on a separate host from the P2J server, then there can be certificate issues as a result.

#4 Updated by Greg Shah over 10 years ago

From Marius on November 4, 2013:

Regarding the implementation on P2J I read your specifications.
Here is what I understood so far:

1. The browser client will open a URL that is serviced by Jetty in the P2J server process. I'll call this the "server jetty". The URL will be something like https://p2jserverhost:jettyport/chui/ (for the AJAX ChUI) or https://p2jserverhost:jettyport/gui/ (for the AJAX GUI).

Q: The "server jetty" is in fact the WebServer or another instance, a new design?

2. Dynamically start a new P2J client process (we already do something very similar in starting "appserver instances")
This will be done by a special launcher process that is there to create child processes under the control of the P2J server
The new client will be started in "browser mode" (ChUI or GUI) and the client code will be the standard P2J java client
That P2J client will start up and will make an initial connection to the server's secure port using the P2J protocol (this is the "client jetty")

Q: The "client jetty" is started similar to ServerDriver#launchAppServer?

3. The P2J client will notify the P2J server about the URL for which the client jetty is listening, this will include a custom/dynamic port number
The server jetty will respond to the original browser request with a page that redirects the browser to the new client jetty URL
The browser will connect to the client jetty, which will send down a page that is the proper AJAX client (ChUI or GUI)
That AJAX client will establish a secure websocket connection to the client jetty

Q: The P2J client is a new process allocated per connection having an embedded jetty server which should deliver
Web Sockets Services and the main page?

Q: The port number should be generated by an algorithm randomly or in sequence?

Q: The jetty protocol is wss over https?

Q: The SSL context is the same as the "server jetty" or if in the directory is registered a SSL context
(keystore and credentials) for this purpose use this one as a first option?

#5 Updated by Greg Shah over 10 years ago

From Greg on November 4, 2013:

Q: The "server jetty" is in fact the WebServer or another instance, a new design?

I don't see any important advantage to have another instance. My plan was to just have a different URL (than the admin console) but to extend the same WebServer.

If you have any reasons to do something different, let me know.

Q: The "client jetty" is started similar to ServerDriver#launchAppServer?

Not exactly. That code is related, but I am referring to the server-side code in AppServerLauncher.java. That is the code that really does the launching work. Specifically, look at builder() and launchWorker().

But the development for client launching part will be done in another task than #2200.

Q: The P2J client is a new process allocated per connection having an embedded jetty server which should deliver Web Sockets Services and the main page?

Exactly.

Q: The port number should be generated by an algorithm randomly or in sequence?

I don't want to allocate it at all. Let the operating system allocate it dynamically. We can read the allocated port number from the resulting socket instance and send it back to the server as part of the complete URL.

Q: The jetty protocol is wss over https?

Let's not call it the "jetty protocol", but rather the "web client protocol".

Yes, it is wss from an https session.

Q: The SSL context is the same as the "server jetty" or if in the directory is registered a SSL context (keystore and credentials) for this purpose use this one as a first option?

Yes, exactly.

#6 Updated by Greg Shah over 10 years ago

From Marius on November 5, 2013:

Today I started to design and implement the AJAX Client Driver.
So far I designed the embedded jetty SSL server and a generic
web socket handler used to register web sockets on the server context.

#7 Updated by Greg Shah over 10 years ago

From Marius on November 7, 2013:

Web Sockets support - the current design

Each client running on server has an embedded jetty server which offer web sockets support.

package com.goldencode.p2j.ws;

1. WsServer - implements the embedded jetty server.
- The server is running on the default SSL context but if a BootstrapConfig structure is provided the SSL
context will be constructed based on this bootstrap configuration.
- The port number could be specified explicit or could be be dynamically allocated by the OS when the port = 0.
- The host name could be null case on which the server will listen on all available addresses.
- On each server a context path (default /) could be defined.
Example:
https://localhost:8443/
https://localhost:8443/contextPath
- The server provide a method to add Handlers for web resources like HTML pages.
- The server provide a method to register web sockets based on a target string and the web socket class.
- All Handlers are registered into the embedded server context.

2. WsClient - is a contract (Interface) that should be implemented by each client.
He provide two methods for start-up and shut-down the embedded server.
It is possible to transform WsClient into an abstract class that must be extended by the client.
This is not a flexible design if the client implementation should extend another class.

3. TestPageHandler - is an implementation of a Jetty Handler for delivering HTML files resources.
This class does not provide placeholder substitution. If we need placeholder substitutions
just extended this class and override the method handleReplacements in order to provide a custom
code for substitution. (see the TestPageHandler class). Each handler define a target string
which is part of the URL path.

package com.goldencode.p2j.ws.test;

This package provide a simple example of a client which expose web sockets services via an embedded server.

1. TestWsClient is a very simple client implementing the WsClient interface.
An embedded WsServer instance is created, configured and started.

2. TestPageHandler is a Handler for index.html file. The file is parsed and some placeholders are
substituted before to deliver the page. This is basically the main page.

3. TestWsServlet is a Web Socket simple class.
Jetty offer the following methods to create Web Sockets:

a. POJO - Using WebSocket annotations.
b. Implements WebSocketListener interface.
c. Extends WebSocketAdapter class.

This design has been tested by creating an instance of TestWsClient inside the WebServer
class after the web server start statement:

new TestWsClient().startup(null);

and found to work properly.

On the web browser open: https://localhost:8443/client/page and click on the link inside the page.

I did also tests having two web sockets registered and found to work properly.

#8 Updated by Greg Shah over 10 years ago

From Greg on November 7, 2013:

Initial Feedback

I realize that this is a work in progress and an early version. Thank you for posting it and the useful description of the approach. I will refrain from a full code review because this is still early and will change a great deal.

I like what you are doing in general. There is some very good stuff here. I have the following questions/comments:

1. WsServer has very little to do with WebSockets. Other than the WsHandler inner class and the addWsHandler() method, the code is otherwise quite generic. In fact, it seems to me that it is very, very similar to our current com/goldencode/p2j/main/WebServer. I prefer to maximize common code here, since the server jetty and client jetty will both have similar configuration requirements and will need to support similar usage patterns.

a. I would like to enhance or extend our current WebServer to be used as both the server jetty and client jetty. This eliminates having a 2nd implementation that is so similar to the code we are already maintaining. To the degree that we need to provide more control over the startup/configuration/shutdown of the WebServer class to make it more flexible, we should do so. It also makes it easier to add WebSockets support to the server jetty, which is something will need in the future as we shift our admin console to AJAX.

b. I'd like to see the WsHandler code moved out of that class and put in its own class. The registration code can be hidden there too (to register itself with the WebServer instance). In addition, please call it WebSocketsHandler because the "ws" abbreviation can mean too many different things.

2. I don't understand the need for the WsClient interface. We can always create a helper class for providing common code for the startup/shutdown of the client jetty. It doesn't need to be in the inheritance hierarchy of the client driver. I prefer that approach because there are less layers of abstraction.

3. If I understand the code properly, all the "static" HTML can be loaded from our p2j.jar, right? That is exactly how we want this to work, since the code will only change when we have a revision of P2J. That avoids maintaining separate files in the filesystem which is very good.

4. I like the handleReplacements() support you have provided. It is useful and will be needed in the future. However, for the specific use case in your test code, I think you can use the javascript location object to get the host, port and so forth.

#9 Updated by Greg Shah over 10 years ago

From Marius on November 8, 2013:

Release candidate after your feedback.

1.a The embedded server was renamed as GenericeWebServer. This class could be extended or embedded.
I changed the old WebServer class to benefit from the new design. Now the WebServer extends
the GenericWebServer and can expose web sockets services.

1.b The Web Socket generic handler is no longer an inner class. The name of the class is PojoWebSocketHandler
I preferred this name because PojoWebSocketHandler already extends the jetty WebSocketHandler class.

2. I called the "jetty client" server Agents. I understood that an Agent is lunched by an instance of a
ProcessBuilder class which is designed to create OS processes. I our case an Agent is a Java class
having a main method and the ProcessBuilder will construct and lunch a command like:

java -cp bin;lib/lib1.jar;lib/lib2.jar com.example.AgentClass
The agent will run as a separate process having its own JVM isolated from other processes.
Using the command line arguments passed (server ports and others) as parameters on the main method the Agent
will be able to build an interface in order to communicate with the server via p2j RMI.
For testing purposes I created a WebSocketAgent which extends the GenericWebServer and have a main method.
Until the luncher of agents will be designed and implemented I tested this agent in the context of the server.

3. The handler for static pages has been designed to load the html pages for the jar file
(a resource on the classpath). Nevertheless other handlers could be desigen to serve
other resource from different URI's.

4. I'm using window.location.host in JS instead placeholders, thanks for this hint!
Only the ${context} placeholder is resolved on the handleReplacements().

Please let me know if you agree the new design.
Also I would like to ask you if I should move the classes from my ws package
to the main package and perhaps the ws.test package to ws.

#10 Updated by Greg Shah over 10 years ago

From Greg on November 8, 2013:

Feedback

This is very good. Some thoughts:

1. Change the "ws" package to "web". This will be the place for generic web server classes.

2. As you get a real client implementation going, you will not need the test package anymore. You can keep it for now, for development purposes, but the final update should not have it.

3. Make sure you are reviewing the ClientDriver, com/goldencode/p2j/ui/chui/ThinClient (in regards to how it integrates with the "drivers" and also how it does much of the startup processing) and the ChUI drivers (com/goldencode/p2j/ui/client/chui/driver/*). These will help you design the changes for how the ChUI web client should be implemented.

4. In regards to coding standards, we now have a 98 character line limit instead of 78 (docs aren't up to date yet). Also, please make sure that all methods and members are javadoc'd. There will be more details on this later, when the code is nearing completion.

Keep going. It is good work.

#11 Updated by Greg Shah over 10 years ago

From Marius, on November 11, 2013:

Today I did a lot of tests regarding the ClientDriver and ThinClient.
I'm able to starts and debug from my IDE both instances, the server and the client.
Here is what I understood from this tests. Please let me know if something are missing.

1. The ClientDriver is a remote client started from a command line which run on separate process having
only one thread, the main thread.

The command line arguments define the ScreenDriver type and parameters.
Also the server host and port are specified as command line parameters.
It is possible to provide the same parameters using a configuration file like client.xml

net:server:host=localhost
net:server:insecure_port=3333

client:driver:type=swing_chui_frame
client:chui:rows=24
client:chui:columns=80
client:chui:background=0x000000
client:chui:foreground=0xFFA500
client:chui:selection=0x0000FF
client:chui:fontname=monospaced
client:chui:fontsize=12

2. From the command line arguments a BootstrapConfig object is constructed used to create, initialize and start
a generic client ThinClient (CoreClient#start).
It's possible to create a remote standalone client running on a separate process like ClientDriver do
or a client running within the server process.

3. Next the UI services are initialized: ThinClient#initializePrep(config, driver, single);
An OutputManager instance wrapping a ScreenDriver object is created.
The OutputManager is used to draw on the screen via an OutputPrimitives interface (ScreenDriver#getPrimitives)
Currently p2j has 2 screen drivers:

- ConsoleDriver - or chui native based on NCURSES package.
- SwingChuiDriver - a Java SWING based client.

I tested both screen drivers and I found to work within ClientDriver.

Also a Java applet ChuiApplet client can be used embedded in an html page loaded by a web browser.

https://localhost:7443/chui

According to my tests this client only draw the main window and a cursor on the top left corner.
It seems that this client is not fully implemented.

4. When the ThinClient instance is created the ClientExports and SessionExports (for static methods) interfaces are exported remotely.
(The SessionExports interface seems to be not used remotely. I didn't found any remote import for this interface).
The registered (exported) ClientExports interface is imported by the LogicalTerminal instance an used to do RMI calls to ThinClient.

5. Next the client is authenticated and a security context for this thread is created.

6. Next finish initializing the thin client: ThinClient.initializePost(session, context)
Imports ServerExports and RemoteErrorData interfaces exported by the remote StandardServer used for RMI calls to server.

7. Finally the MainEntry interface exported (registered) on the StandardServer is imported, a
ClientParameters is constructed having the converted 4gl application
command line parameters and the remote application is started.
In my case Ask#execute is called which is the Java converted version of the 4gl Ask.p

// call the application
running = main.standardEntry(params);

8. The client code waits here until the user will terminate the running application.

9. When the application is terminated by the user, some clean-up statements are executed
and finally the client exits: System.exit(0);

As a conclusion of my tests, I have a question:

I think that we have to design a new ScreenDriver having an embedded server
used to communicate with the JS code which run inside the HTML page on the web
browser via web sockets.
In this case what kind of UI components are used inside the HTML page?

a - HTML specific tags?
b - JS based?
c - java SWING applet?
d - others?

#12 Updated by Greg Shah over 10 years ago

From Marius, on November 11, 2013.

#13 Updated by Greg Shah over 10 years ago

From Greg on November 11, 2013:

Before I review the code, I will provide some comments and answers to note 10.

The ClientDriver is a remote client started from a command line which run on separate process having only one thread, the main thread.

Yes, ClientDriver is a separate Java process that runs in its own JVM, outside of the P2J server.

Yes, it is remote in the sense that it communicates with the P2J server using our own proprietary remote object protocol.

No, it does not only have 1 thread. There is a type-ahead (key reading) thread that supports our requirement to provide asynchronous interrupts for CTRL-C. And the network socket that connects the client to the P2J server has 2 threads (1 reader and 1 writer). The main thread is in fact the primary thread for doing real work. It is enslaved to the "conversation mode" that we implement between the client and server. See the Conversation class in the net package.

FYI, there is a special-purpose mode in which the ServerDriver can be run as both client and server in one JVM. This is not something we use often and no customers use it today.

The command line arguments define the ScreenDriver type and parameters.
Also the server host and port are specified as command line parameters.
It is possible to provide the same parameters using a configuration file like client.xml

Yes, mostly right. I would note that the bootstrap config can be given as a file and any values in it can be overridden on the command line. The idea is to use that for values that are needed for early configuration of the client (or server, since it can be used there too). Generally, we try to push as much configuration into the P2J server's directory.xml (often just referred to as the "directory") as possible. This maximizes central management of the configuration and can make deployments easier.

For programs that use the P2J classes to make their own connection to a P2J server, the bootstrap configuration can be completely built and controlled under program control.

According to my tests this client only draw the main window and a cursor on the top left corner.

That may be a regression or perhaps it is some applet problem on your system/browser. When I put that applet in, it was working without issues. However, if you were trying it with MAJIC, it would not work today because MAJIC is very dependent upon executing child processes (and other direct filesystem access). That is not possible in the applet approach.

The SessionExports interface seems to be not used remotely. I didn't found any remote import for this interface

No, it is in use. Please see com/goldencode/p2j/util/SessionUtils.java. It is used to "push" some 4GL session-level configuration down to the client so that to the extent that the UI can render or otherwise need to use this configuration, it is in synch with the state on the server.

The registered (exported) ClientExports interface is imported by the LogicalTerminal instance an used to do RMI calls to ThinClient.

Almost. I would not call our approach RMI, which is a very specific Java implementation. However, the idea is the same. We have our own "remote object" protocol (sometimes called the "DAP"/distributed application protocol, but mostly just called "the protocol"). This is the equivalent of Java's RMI, but it has better security and it is easier to implement as a developer. It also doesn't need a name registry process to be running.

But otherwise, your description is correct.

Also note that we instantiate some other classes (e.g. FileSystemDaemon, StreamDaemon, ProcessDaemon, MemoryDaemon...) which also register client-side services that can be called from the server. These are the daemons that allow the client-side process launching, file system access and so forth.

The remote object protocol is designed such that either side of the link can export an API. This peer-based or birdirectional RPC is actively used.

Next the client is authenticated and a security context for this thread is created.

Yes. Note that the context is on the server. On the client, there is only one "session" and the security manager is intentionally a null thing. Since all the actual converted code is running on the server, in a session that has a specific context associated with it, this is OK.

The client code waits here until the user will terminate the running application.

Not exactly. The main thread will be unblocked to execute remote API calls through our exported objects (like ClientExports). And with validation expressions and UI triggers, called client-side code can recursively call back to the server, which can then call back down to the client and so forth. In other words, there can be a complex stack of operations where the control flow of execution for the program will shift back and forth between the client and server, possibly on a deeply nested basis. Ultimately, these nested calls will eventually return and pop off the stack until the program exits, returning here for the main thread to exit (or to restart depending on the "stop disposition").

I think that we have to design a new ScreenDriver having an embedded server used to communicate with the JS code which run inside the HTML page on the web browser via web sockets.

Yes.

a - HTML specific tags?

No.

b - JS based?

Yes. But this is not part of this task. The short answer is that we will use the Canvas interfaces to directly draw our own "Window" implementation and everything that is inside of it. There will not be any actual browser components or plugins used.

c - java SWING applet?

No.

d - others?

No.

#14 Updated by Greg Shah over 10 years ago

Feedback 1111a

I like it. I look forward to seeing the client side fill out.

#15 Updated by Marius Gligor over 10 years ago

The archive mag_upd20131112a.zip contains the first implementation for the Ajax screen driver.

AjaxScreenDriver has an embedded web server inside used to deliver a main HTML page and offer support for web socket services.
Web sockets are created by a custom WebSocketCreator object which allow the injection of a screen driver instance inside the created web socket.
The AjaxScreenDriver follow the SwingChuiDriver model but instead to use a ChuiSimulator it use an AjaxWebSocket object.

All the API calls on AjaxScreenDriver and AjaxOutputPrimitives are delegated to the AjaxWebSocket.
Here we have to implement the interface between the screen driver and the JS client via web sockets.

However the Ajax screen driver has a different paradigm.

1. The web socket communications are asynchronous messaging operations.
Here we have to find a solution in case we need a synchronous operation which should wait until an event occur on the browser.

2. The communication between WebSockets on the server and WebSockets on the client (browser) is based on text or binary messages.
We have to define a format for our messages. Because the end client is a JS code, I suggest to use text messages in JSON format.
In this case we have to add support for JSON serialization / deserialization to our implementation for example a Jackson processor which is widely used.

#16 Updated by Greg Shah over 10 years ago

Code Formatting Notes

The following are some general (but minor) problems with the code formatting.

1. Where possible, reduce lines.

This:

   public AjaxMainPageHandler(String targetRoot,
                          String skeletonPage)

can be:

   public AjaxMainPageHandler(String targetRoot, String skeletonPage)

This is preferred and it is OK because it doesn't cross the 98 character length.

2. Don't use more than 1 consecutive blank line (sometimes you are putting 2 lines in between methods).

3. There should always be 1 blank line in between methods (sometimes you don't have any blank lines at all).

4. Don't put a blank line between the end of the last method and the end of the class.

5. Make sure that there is proper spacing when using operators and/or control keywords. For example, if( should be if (.

6. For newly created files, the copyright date should be simply 2013 instead of a range (probably this is just a copy/paste error).

#17 Updated by Marius Gligor over 10 years ago

code formatting

#18 Updated by Greg Shah over 10 years ago

Code Review 1112a

As we are just starting on the path to the web driver(s), we will check something in soon that is just a step along the way. Your work so far is a good start. I will answer some of your questions in a separate note (I have already been doing quite a bit of thinking on these questions, I just haven't documented all of my thinking yet).

Before we move forward with the first version, please consider the following ideas:

1. We will have at least 2 web drivers (ChUI and GUI). I would like to maximize the common code where possible. It seems to me that the embedded web server, web sockets/protocol support and some of the javascript implementation can be common. For example, on the javascript side I would expect the key processing to be common code between both ChUI and GUI. To make common code we might need to put the common portions in a different package and the design might need to be a bit different.

2. We don't need to implement a real driver yet, but stubbing it out is a good idea. However, it is important to know that we don't want to use the websocket in such a granular way that every call to the ScreenDriver or OutputPrimitives interfaces will trigger a "trip" down to the browser. For example, we will want to build up the changes to the virtual screen and only send them to the browser when sync() is called. Please consider how we might rework the driver to minimize the trips to the browser.

3. Although I keep using the name ajax to describe all of this, I don't think it is the best description. I think WebClient is a better description for the common portions and with ChuiWebClient and GuiWebClient being more specific names.

#19 Updated by Greg Shah over 10 years ago

Code Review 1112b

I like the changes.

#20 Updated by Greg Shah over 10 years ago

1. The web socket communications are asynchronous messaging operations.
Here we have to find a solution in case we need a synchronous operation which should wait until an event occur on the browser.

Yes, we will have to solve this when we get to working on the protocol. We don't have to solve this for #2200.

2. The communication between WebSockets on the server and WebSockets on the client (browser) is based on text or binary messages.
We have to define a format for our messages. Because the end client is a JS code, I suggest to use text messages in JSON format.
In this case we have to add support for JSON serialization / deserialization to our implementation for example a Jackson processor which is widely used.

I will post more details in regard to the protocol. But I am pretty convinced that the protocol will have to be binary.

The P2J architecture introduces a logical separation between the business logic and the UI, which does not exist in the original 4GL environment (where everything runs in the same process). We found that we did have to optimize our code in order to solve some performance issues that were due to this architectural decision. In particular, we found that optimizing the bytes transferred (between the P2J server and the P2J clients) was very important for reasonable performance of the UI. Our protocol was already a binary protocol. But our first version used the Java Serializable support. We were able to make major performance improvements by implementing Externalizable instead (and implementing our own read/write functions in all of our objects to be transferred).

The web client introduces yet another separation. It will be well worth our time to make the protocol as efficient as possible. Using a binary approach will very likely be required. I plan for us to use that from the beginning. I do understand that it has costs on the javascript side, but I think this is a price we must pay.

#21 Updated by Greg Shah over 10 years ago

Task #2201 is meant to take the next steps beyond #2200. In #2200 the embedded web server is enabled in the client. In #2201, that client is started based on a request to the server jetty. The client's SSL context will need to be properly setup from the server and the host/port for the client's embedded web server must be passed back to the server so that the web request can be redirected.

#22 Updated by Marius Gligor over 10 years ago

1. I've created a new Chui screen simulator which use the web socket for remote communication.
Basically the simulator is an in memory controller for a character terminal video memory (similar with ChuiSimulator on swing client).
The memory is organized as a 2d array having a number of cells equals to number of rows multiplied by the number columns.
Each cell holds the character that should be displayed and the other attributes like colour.
The Chui screen driver send commands to this simulator for simple draw operations on the video memory.
The screen driver is UI widgets aware, in sense that he only draw text (characters) not UI widgets.
Almost all draws operations are done only in memory. When a full screen repaint ( sync() ) is compulsory
the screen will be send over the WebSockets to the remote peer (web browser JS code)
Also the implementation of other operations like beep() should be delegated to the remote peer.
At this stage the server web socket has as a minimum a FIFO key buffer (Queue) on which the key code pressed by user are stored.
The key events are send over the network from the remote peer to server.
Obviously a real implementation will refine the current design.

2. I found no GUI implementation on the current p2j. The screen driver for GUI should be designed from scratch.
Are you intended to use the same pattern design as a CHUI driver, or we have to design something else?

3. The name of the web screen driver is ChuiWebDriver because the other drivers has similar names.
My intention is to use a ChuiWebClient object as a web client luncher similar to the design in ClientDriver.
Is this a correct design, to implement something like ClientDriver but without a main entry?

4. Regarding the issue #2201 I have to design a new jetty Handler for a request like https://<server>:<admin_port>/chuiweb
This Handler should start a ChuiWebClient, get the embedded server URI and redirect the request to this URI.
We can do the redirection in two ways. Which method do you prefer?

a) By returning a web page having this <meta http-equiv="refresh" content="0; url=https://<server>:<port>/<context>/<page>" /> 
tag inside the head section. The page could be an html file or could be constructed in memory.
b) By calling HttpServletResponse#sendRedirect(String URL) inside the Handler#handle(...) method.
This method does not use a html page for redirection.
However in case of an error on both cases we have to return an error page in order to inform the user
about the error. This page should be also constructed in memory.

#23 Updated by Greg Shah over 10 years ago

Code Review 1113a

This is moving in exactly the direction I was looking for. Very good.

1. I see that there is quite a bit of copied code in ChuiWebSocketSimulator from the Swing ChuiSimulator. It seems to me that it would be better to have an abstract base class that both can derive from, in order to maximize common code. Actually, let's use the following naming:

ui/client/chui/driver/ChuiSimulator - abstract base class
ui/client/chui/driver/web/ChuiWebSimulator
ui/client/chui/driver/swing/ChuiSwingSimulator

I realize that ChuiWebSocketSimulator would no longer be able to extend the WebSocketAdapter. But I think the web sockets part is very small and can be handled external to the simulator functionality.

2. It seems like SwingChuiPrimitives can be moved/renamed ui/client/chui/driver/ChuiPrimitives and it can be made as a common class used by both the swing and web implementations. I don't think there is anything unique about ChuiWebPrimitives, right?

3. I would like to see if ChuiWebSocket can be made generic for both the ChUI and GUI implementations. At a minimum, most functionality can be in an abstract base class, with more specialized subclasses to handle any unique requirements. This would mean that it would live in ui/driver/.

4. I think the same idea as in number 1 above can be done for ChuiWebDriver/SwingChuiDriver. The abstract base class can be ui/client/chui/driver/ChuiScreenDriver and it would implement ScreenDriver.

#24 Updated by Greg Shah over 10 years ago

I found no GUI implementation on the current p2j. The screen driver for GUI should be designed from scratch.

Correct. But that is work for a future task. Right now we will implement: code that can be common for any web driver + chui web driver, while refactoring the swing chui driver to share maximum code between swing chui and web chui.

We should consider using TerminalOptions (and other classes) as common code which may be swing-specific today. It seems to me that we some things like Color instances can be stored in the P2J client as Swing-specific values. But then we would translate this into a value (like #FFFFBF) that can be sent down to the web client, when needed.

Are you intended to use the same pattern design as a CHUI driver, or we have to design something else?

Yes, but the GUI driver interface will by nature be different. I hope that we can have a super-interface that is shared between both chui and gui. For example, the key processing is something that can probably be the same between both driver interfaces.

I also am worried about the optimal place to introduce the driver interface in the GUI environment. There are multiple choices:

  1. Provide drawing primitives (lines, boxes...) and allow the widgets to call these directly. The big advantage is that it would be easy to implement these primitives on both Swing and in Javascript canvas and there would be massive common code for the hard parts of the drawing. In particular, the widget-specific drawing logic would be common code. The big downside is that I expect this to perform very poorly when we add the extra split to remotely drive the browser drawing.
  2. Provide widget-specific drawing methods in the abstraction layer and then implement widget-specific drawing logic separately in swing and javascript.

Both ideas might be amenable to the optimization of sending a list of the drawing operations down to the javascript client. For example:

- draw a button with this rectangle, colors and text
- draw text in this font at this location
- draw a box of this rectangle and color
...

Then the entire list can be sent at one time and there won't be massive "back and forth" from the P2J client to/from the browser. Many small trips is the thing that really kills performance in this case. One round trip with a larger payload will significantly outperform the many small trips case.

This example is showing the list as a set of widget-level drawing primitives, but we could send a lower-level set of primitive operations instead (draw a line of this color from point a to point b, draw a rectangle with this line color and this fill...).

My intention is to use a ChuiWebClient object as a web client luncher similar to the design in ClientDriver.
Is this a correct design, to implement something like ClientDriver but without a main entry?

Actually, for now let's just extend our current approach. ClientDriver/ThinClient will use different drivers based on bootstrap configuration. Don't add a dedicated client driver.

We can do the redirection in two ways. Which method do you prefer?

Are there any downsides to using HttpServletResponse.sendRedirect()?

#25 Updated by Marius Gligor over 10 years ago

1. The server web socket is created only when the remote user will open the communication with him.
On the other hand the simulator MUST be created and is created when the client is lunched.
That's why the simulator does not extends the web socket.
However it is possible to change the web socket creator to return an existing instance
and in this case I think that is possible to extend the web socket in simulator.
I will think to that issue to find the better solution.
When the web socket is open the simulator is notified about this and the simulator send the
screen status down to the web browser in order to be rendered on the web page via JS.
I will try to make the current implementation more abstract as you already suggested.

2. I've started also to work on redirection issue. So far I created a Handler
which create an instance of the ChuiWebDriver, start the embedded server and
redirect the request to the embedded server URI. On error will return a short page
with a message informing the user about this. I did some tests and the redirection
works fine using the response sendRedirect method. I added to the server directory.xml
a new entry chuiWeb like chuiApplet. I found a minor bug on current implementation:
The chuiApplet is using /chui as context root and I want to use /chuiweb
Because the check on chuiApplet is target.startsWith(/chui) is not possible to
handle both /chui and /chuiweb. Finally I used /webchui instead /chuiweb

3. Something remain unclear on my mind at this point. If we aren't use a mechanism
like ClientDriver to start a client how we will connect our screen driver with the
converted 4gl application?

#26 Updated by Greg Shah over 10 years ago

in this case I think that is possible to extend the web socket in simulator

I don't have a problem using the WebSocketAdapter in our solution (or WebSocket directly). I just prefer to separate the use of it from the inheritance hierarchy of the simulator.

The chuiApplet is using /chui as context root and I want to use /chuiweb
Because the check on chuiApplet is target.startsWith(/chui) is not possible to
handle both /chui and /chuiweb. Finally I used /webchui instead /chuiweb

Let's just use /chui and eliminate the ChuiApplet. In practice, there is no good reason to use ChuiApplet when you can use our web client instead. Eliminating the applet is a good thing.

If we aren't use a mechanism like ClientDriver to start a client how we will connect our screen driver with the

converted 4gl application?

The proper driver configuration would be passed in a bootstrap config instance when the client is launched. You will have to modify OutputManager.newInstance(), which is called from OutputManager.init() which is called from ThinClient.initializePrep().

#27 Updated by Marius Gligor over 10 years ago

The WebSocketAdapter was used only for notification mechanism because WebSocketAdapter implements WebSocketListener.
A better solution is to design a new interface just for notifications and eliminate the inheritance of WebSocketAdapter.
The class ChuiSimulator from swing based driver extends JComponent and this is a problem because we cannot extends another class like an abstract or base class.

I know about the OutputManager changes that we have to made in order to add our screen driver.
In this case I think that we have to use the ClientCore.start(...) method like ClientDriver finally do.

#28 Updated by Greg Shah over 10 years ago

In this case I think that we have to use the ClientCore.start(...) method like ClientDriver finally do.

I was assuming we would use the same ProcessBuilder mechanism that we use for appserver agents today. It just builds the Java command line up dynamically and then runs the program. There is no reason we can't use ClientDriver from there. Is there some other reason ClientDriver won't work?

#29 Updated by Marius Gligor over 10 years ago

I did more changes in the structure of the project to make the implementation more generic and abstract.

1. The ChuiSimulator already extends JComponent and cannot extend another abstract class.
However I used the ChuiSimulator as a base class for ChuiWebSimulator.

2. Having a generic ChuiSimulater the ChuiPrimitives became also generic
and common for both swing and web screen drivers. The SwingChuiPrimitives is no longer needed and has been deleted.

3. The ChuiWebSimulator is also decorated with WebSocket annotations, that is, he play also the role of a web socket.
The ChuiWebSocketCreator has been changed to return an existing instance and not to create a new one because the
ChuiWebSimulator must be created earlier and exists at the moment when the jetty server call the WebSocketCreator
asking for a WebSocket instance.

4. A ChuiScreenDriver has been created as a base class for both SwingChuiDriver and ChuiWebDriver.

5. I changed also the OutputManager and I added code to use the new web screen driver as having "chui_web" name.

6. ChuiWebHandler class is jetty Handler used for start the web client and redirect the HTTP request to the client embedded server URI.
At this moment here is a simple test code used just for testing purposes and should replaced with the process spawn code.

7. I tested also the ClientDriver from command line using chui_web driver and I found that the the web client work properly,
the connection with the remote 4gl application is done and the client waits for ended of the converted application.
However I found also that the embedded web server does not work.
The server is started and listen for connections but the web browser cannot download the main page.
The browser said the the server does not accept encrypted connection and I think that the embedded server has no SSL certificate.
If you have any hint regarding this issue please let me know.

8. After all this changes I tested also the swing console terminal and so far I found no errors.

#30 Updated by Marius Gligor over 10 years ago

Regarding my tests with ClientDriver and "chui_web" screen driver I used the configuration
file client.xml and the key stores from P2J_HOME/testcases/simple/client_strong.
I saw inside the BootstrapConfig informations related to key stores as well as access passwords.

Also I did tests without this configuration file but in this case the BootstrapConfig does not
contains informations regrading SSL.

#31 Updated by Marius Gligor over 10 years ago

#32 Updated by Greg Shah over 10 years ago

Code Review 1114a

1. The ChuiWebSocketCreator has nothing that is specific to ChUI. Let's make it generic and put it in the ui/driver/ package so that the functionality can be shared between ChUI and GUI.

2. There are minor code formatting issues in ChuiWebHandler.

3. As mentioned in the previous notes, we don't need the ChuiApplet any more. Please remove that support (including from StandardServer).

4. num should not be set to 1 at the beginning of the initialize() method inside StandardServer.registerDefaultServices().

5. The "Module" name at the top of ChuiScreenDriver (and also ChuiPrimitives) is incorrect.

6. Please duplicate the javadoc for interface methods that are implemented instead of just referencing it using at-see (e.g. in ChuiScreenDriver).

7. I prefer the ChuiScreenDriver.beep() implementation to be moved into SwingChuiDriver. There is no case where that code can be shared and leaving in the base case just raises questions on the part of the reader.

8. ChuiWebHandler seems pretty generic and not specific to ChUI. Let's just rename it WebHandler.

9. About the SSL problem, it is expected.

The server is started and listen for connections but the web browser cannot download the main page.
The browser said the the server does not accept encrypted connection and I think that the embedded server has no SSL certificate.
If you have any hint regarding this issue please let me know.

We need to find a way to send the P2J server's SSL certs/context down to the P2J client so that it can be reused. As for where the server gets it, please see SecurityManager.getSecureSocketContext() and the TransportSecurity class in that same package. Some changes will be needed, but the core idea is that we DO NOT want to have to configure keystores/truststores on each client. Instead, we want to leverage the server's SSL context, but use it on the client. Changes will be needed in SecurityManager and TransportSecurity to make this happen.

#33 Updated by Marius Gligor over 10 years ago

Changes after Code Review 1114a.

#34 Updated by Greg Shah over 10 years ago

Code Review 1114b

It looks good. Please get this runtime regression tested.

I assume that the direct reference to ChuiWebDriver from WebHandler will be eliminated when the proper client spawning is working. Is that correct?

#35 Updated by Marius Gligor over 10 years ago

Yes, the reference to ChuiWebDriver from WebHandler will be eliminated and replaced with code to spawn the client.

#36 Updated by Marius Gligor over 10 years ago

  • File regression_tests_reports_01.zip added
  • File regression_tests_reports_02.zip added

1. Here are the results of the regression tests. Seems to be expected errors.

2. I did also tests on my workstation like on the previous regression tests.

- Admin console works properly on my workstation.
- I tested also the new WebDriver by adding the following node into gso-directory.xml file:
<node class="container" name="chuiWeb">
<node class="boolean" name="enabled">
<node-attribute name="value" value="TRUE"/>
</node>
<node class="integer" name="rows">
<node-attribute name="value" value="24"/>
</node>
<node class="integer" name="columns">
<node-attribute name="value" value="80"/>
</node>
<node class="string" name="background">
<node-attribute name="value" value="0x000000"/>
</node>
<node class="string" name="foreground">
<node-attribute name="value" value="0xFFA500"/>
</node>
<node class="string" name="selection">
<node-attribute name="value" value="0x0000FF"/>
</node>
<node class="string" name="fontname">
<node-attribute name="value" value="monospaced"/>
</node>
<node class="integer" name="fontsize">
<node-attribute name="value" value="12"/>
</node>
</node>

The first tests FAILS due to the missing of index.html resource file into p2j.jar
The ant script (build.xml) should be updated to copy this file resource into the build
and finally into the jar file.

I added the file index.html to p2j.jar "by hand" and finally it's works fine.
Should I change the build.xml file now or we have to do that when we'll finish the implementation?

#37 Updated by Constantin Asofiei over 10 years ago

Marius Gligor wrote:

1. Here are the results of the regression tests. Seems to be expected errors.

Do not update test results here. If you are unsure if you have a real or not error and want us to look over it, send the files via email.

- I tested also the new WebDriver by adding the following node into gso-directory.xml file:

Are these changes required for MAJIC's Admin Console?

The first tests FAILS due to the missing of index.html resource file into p2j.jar

All look like false negatives.

Should I change the build.xml file now or we have to do that when we'll finish the implementation?

Do change it now.

#38 Updated by Constantin Asofiei over 10 years ago

  • File deleted (regression_tests_reports_01.zip)

#39 Updated by Constantin Asofiei over 10 years ago

  • File deleted (regression_tests_reports_02.zip)

#40 Updated by Marius Gligor over 10 years ago

The changes does not affect the the adminConsole applet.

The chuiApplet is no longer used, should I delete the files
related to this applet from project?

#41 Updated by Greg Shah over 10 years ago

The chuiApplet is no longer used, should I delete the files related to this applet from project?

Yes.

#42 Updated by Greg Shah over 10 years ago

Please fix the build.xml and re-do your manual testing. If that works, then you can check in the update and distribute it.

#43 Updated by Marius Gligor over 10 years ago

The following files should be deleted:

ChuiAppletHandler.java ChuiApplet.java and chui_applet.html

Also I have to do some changes on OutputManger.java and delete the code related to CuiApplet.java
and in the file build.xml to eliminate the copy of chui_applet.html to build env.

The file OutputManger.java already has some changes that should be commited.

Should I redo the regression tests or is enough to test only locally?

#44 Updated by Marius Gligor over 10 years ago

The archive contains the ChuiWebDriver implementation based on jetty web sockets.
Passed regression testing, committed to bzr revision 10412. Refs #1811.

NOTE: The SWING applet is no longer used and the files related to the applet has been deleted.

The following node should be added to the server directory.xml
Basically is the same as the "chuiApplet" node.
Since the applet is no longer used you should rename the "chuiApplet" node to "chuiWeb" instead to add a new node.
If your directory.xml does not contains a "chuiApplet" node add the "chuiWeb" node.

<node class="container" name="chuiWeb">
<node class="boolean" name="enabled">
<node-attribute name="value" value="TRUE"/>
</node>
<node class="integer" name="rows">
<node-attribute name="value" value="24"/>
</node>
<node class="integer" name="columns">
<node-attribute name="value" value="80"/>
</node>
<node class="string" name="background">
<node-attribute name="value" value="0x000000"/>
</node>
<node class="string" name="foreground">
<node-attribute name="value" value="0xFFA500"/>
</node>
<node class="string" name="selection">
<node-attribute name="value" value="0x0000FF"/>
</node>
<node class="string" name="fontname">
<node-attribute name="value" value="monospaced"/>
</node>
<node class="integer" name="fontsize">
<node-attribute name="value" value="12"/>
</node>
</node>

#45 Updated by Marius Gligor over 10 years ago

Here is the first implementation of our process spawner. It was tested and found to work properly.
Bellow are some details related to the most important solved issues.

1. The server security context is exported via command line by introducing 3 new parameters:

ssl:keystore:filename
ssl:password:keystore
ssl:password:keyentry
These parameters are constructed from the server BootstrapConfig structure (SpawnerImpl#exportSslContext).
On the client side using the client BootstrapConfig object a SslContextFactory instance is constructed
(see GenericWebServer#getSslContextFactory) using the exported server SSL context parameters.

2. The Spawner is a singleton design pattern.
The Spawner register a Remote Object Network Server used to receive a notification from client
which must to inform about the embedded server URL on which the client listen via a notification.

When a new web client is spawned a command line for a Java application is constructed.
Next a ProcessBuilder object is used to start the process and wait an amount of time for client notification.
When a notification is received or at the end of the waiting time (time out) the spawner
return the remote embedded server URI or null if no notification is coming.
Also if no notification is coming the spawned process is destroyed.

IMPORTANT: We have to implement also a mechanism to terminate a spawned process when the user close the browser
without to explicit terminate the remote application. In this case the spawned process remain active in memory
and could be accessed by any other user which guess the URI of the embedded server.

#46 Updated by Greg Shah over 10 years ago

Code Review 1118a

There is some good work here, but this still needs some work. If anything, this is a good start that exposes some core design issues that need to be resolved.

1. The singleton design will not scale properly. The approach you have used will only allow 1 client to be started at a time and it will block until the client has initialized.

2. The current design will only work when run with the server's user.dir as the client's current directory. That cannot be a dependency because most apps have very specific client-side requirements for current directory and they would never let each user have a current directory as the same directory in which the server gets all its configuration.

Please review the client.sh (used in Majic) very carefully. These same features need to be exposed when spawning clients. This will be tricky because there is a "chicken and egg" problem. In our standard client model, the user has already logged into the system and thus provides the context (permissions, home directory and so forth). For this to work in our approach, we may need some scripting help at the OS level. We also may need to know what the OS-level userid should be for each new session. How do we get the OS user context established for each new session?

This opens some major security questions. We definitely cannot run in the same permissions/context as the server, but when we start like this that is exactly what happens.

Right now the implementation assumes the clients will always run on the same system as the server. Many of the above requirements will probably require a remote spawner (one that is not in the server's process).

3. Please plan for the server's SSL context to be sent down to the spawner using some form of serialization. This eliminates the need for direct access to the server's current directory and configuration.

4. The connection setup is hard coded to insecure sockets (from the spawned P2J client to the P2J server) which is not something we want to do. It needs to be configurable, while defaulting to be secure.

5. It is also important to note that any environment that is configured to actually force a login (like MAJIC) will fail in the current ClientCore design. The reason is simple: one must have the URL sent back to the server BEFORE the login can occur because in the "normal" case, the user must be prompted for login credentials. But since you are calling a remote method on the server in order to pass the URL back, you are assuming that the P2J session is already setup. That session is created in the connectDirect() call. Your tests worked because you used the simple server environment that has a neutered security environment.

6. The "client.xml" is also something that should be configurable. The fact that we commonly use that filename is purely a convention, but it should not be a requirement.

7. The user.dir and other JVM parms (heap, parm gen...) are things that will have to be configurable from our directory instead of hard coding them.

8. Code formatting and coding standard problems:

  • missing header entries
  • missing public keyword on interface methods
  • OnRemoteServerListen should be onRemoteServerListen
  • wrong sorting of methods by access mode and static/instance

#47 Updated by Marius Gligor over 10 years ago

1. I used a singleton design because I cannot export the Spawner interface on multiple instances.
Is any method to export the same interface per instance?

2. What I understood from your description is that the new spawned process MUST run on the OS user environment.
It's not clear for me why and how to do that because the user is virtually a HTTP request that is coming from
the local or from a remote machine on the network. Our WebClient just offer services via web sockets.
The users have no other controls over the running clients.

Should we spawn the process on a remote machine?
How to do that because the p2j.jar and dependencies in this case must to be on the remote machine?
The web client is running on the same machine where the server is running.
However the single parameter in CoreClient is false.
  • @param single
  • true to startup within the server process which
  • must bypass the shared infrastructure initialization. Use
  • false for the normal client JVM startup.
Could you please explain me what is the difference between single=true and single=false?
We are using a Java ProcessBuilder on our Spawner and we always create a new process which
is a separate process from OS perspective. Only the process OS context is the same as the server context.
Basically instead to use server threads which runs within the server process we use a separate process for each request.
Why this model is not good for our web client?

3. I have to think about this, because is related to 2.

4,6,7. I will change the design to provide configuration for arguments and not hard codding.

5. Here I have also a lot of questions.
- It is possible to use your RemoteObject design to access the remote server before client authentication?

This snippet of code is from ClientCore class.
// establish or re-establish a session with the P2J server
// (this handles authentication too)
session = sessMgr.connectDirect(new InterruptedExceptionHandler(), null);
- connectDirect handles authentication too?.
- should our web client provide an authentication web page too?
Could you please offer me more details because the design of this part is also unclear for me.

#48 Updated by Marius Gligor over 10 years ago

Another important design issue.
When a request is made at https://&lt;server_host&gt;:&lt;admin_port&gt;/chui the request is handled by the WebServer instance.
The WebServer calls Handler#handle on each registered handlers and wait until return from handle method.
The base.isHandled() flag is checked after each call. If this flag is true that means that the request
was handled and a response was sent back to the requester and the web server stops this process here.
If all handlers on the chains are called and no one return base.isHandled() as a true value an default error page
is returned as a response by the jetty web server.

Because each handler is a unique instance the access to handle method is serialized.
Even more on our case we have to take a decision before to send a response back to the user.
This means that we always have to wait for a client response in order to take the decision before sending the response back to the user.
On other words waiting on Spawner or waiting on WebHandler has the same effect, serialization access.
Due to this constraints I don't think that is possible to create web clients simultaneously.

#49 Updated by Greg Shah over 10 years ago

1. I used a singleton design because I cannot export the Spawner interface on multiple instances.
Is any method to export the same interface per instance?

You can export an interface using the instance model (RemoteObject.registerNetworkServer()) which registers an instance of an object that implements the interface(s).

You can also export an interface using the "static" model (RemoteObject.registerStaticNetworkServer()) which registers a class that has static methods whose signatures that match the interface(s) exactly (of course we can't use "implements" with static methods, but this is essentially the same thing).

Either way, there is only 1 registration/export of an interface per JVM. Please read through the RemoteObject code to get the idea.

My concern is not so much the use of a singleton itself, but really it is how you are synchronizing on the class and assuming only one new client is launched at a time. Regardless of the exporting method, you will need to change how the spawning and waiting/notification works to eliminate the "one at a time" approach.

What I understood from your description is that the new spawned process MUST run on the OS user environment.

Correct.

It's not clear for me why and how to do that because the user is virtually a HTTP request that is coming from the local or from a remote machine on the network. Our WebClient just offer services via web sockets. The users have no other controls over the running clients.

Yes, I had a hard time sleeping last night because I was thinking about this exact issue. :) But I did come up with an approach.

Consider that in our P2J client, we have native dependencies. Please look in src/native/ to see the level of functionality that is there. This support is needed because of features that the 4GL provides which cannot be exactly duplicated using J2SE features. On Linux/UNIX, our P2J build creates a libp2j.so and on Windows we create a p2j.dll. We access this DLL ONLY on the client (the P2J server does not use it). The access is through JNI.

On Linux/UNIX, the system is inherently multi-user. There are features built in to allow multiple users to login simultaneously. More importantly there are features to allow processes to change their user context. There is the su command and there are APIs such as setuid.

On Windows Server, I suspect that similar facilities exist as provided by the terminal services.

When needed we can and will add native code helpers to this JNI library to allow us to integrate/call such facilities.

I think we should be able to do something like the following:

  1. The server jetty + initial web page returned will not redirect immediately. Instead, in the common case it will have to create a dialog with the user to obtain the login credentials in a secure manner and submit them to the P2J server.
  2. The P2J server will spawn a new client process which must essentially login/change user context using the given login credentials. We will most likely have to have an alternate approach for guest access (and possibly other scenarios), but we can deal with that later. The result is a process that is running in the proper user's context.
  3. Inside that process that has the correct context, the P2J client must be started. It is possible that we might do this the other way around (spawn a new P2J client in the server's context and then shift context via API calls) BUT there are some sensitive security issues that must be handled in such a case. The new context must not have access to sensitive data/environment/configuration AND it must not be possible to shift the effective user context back to the original server's context.
  4. From there the client jetty must be initialized and we must send the URL back to the server jetty which will send the redirect back down to the client and the rest works as originally discussed.

On Linux/UNIX there are some potential issues with ensuring that there is a "controlling terminal" or pty (pseudo-terminal) associated with every client process that is spawned. The system will not allow interactive programs (or programs that do more with a UI than just read/write to STDIO) to be launched from parent processes that are not setup up with the proper terminal environment. 4GL apps frequently do start child processes that have such requirements. We must make sure the child processes can handle such requirements.

We also will need to find a way to redirect the I/O to/from such programs and the remote javascript "terminal". I haven't fully gotten my head around how we do that, some research will be needed there too.

We also have some special requirements for how we trap exceptions in the client.sh script (on Linux/UNIX), which we will need to duplicate.

On Windows, I'm not really sure what issues we will encounter.

How to do that because the p2j.jar and dependencies in this case must to be on the remote machine?

Yes, this is the assumption.

In such a case, I think we must have a special "client" process on that remote machine that is connected to the server. This process would be able to do the spawning.

The web client is running on the same machine where the server is running.

Sometimes this will be good. But we will have some customers where this is not acceptable.

However the single parameter in CoreClient is false.

Yes, of course. This is correct.

Could you please explain me what is the difference between single=true and single=false?

single=true means that the server process and client process are the same. This is a special mode where there can only be 1 client, ever. This is not really used in production.

Using single=true is not something we are going to do with the web client.

We are using a Java ProcessBuilder on our Spawner and we always create a new process which is a separate process from OS perspective.

Yes, this is good.

Only the process OS context is the same as the server context.

This is what we must change.

Basically instead to use server threads which runs within the server process we use a separate process for each request.

Not really. We already use a separate process today for the P2J client. This is just a different way to launch it and interface with it.

The user still will have a session with the P2J server (the P2J client will have this session actually) and that session will still have threads running on the server on that user's behalf for all business logic and database access. Only the UI and the client platform dependencies (e.g. accessing files, launching child processes/shell commands, using sockets or direct memory access...) will be done in the P2J client (just as it is done today).

It is possible to use your RemoteObject design to access the remote server before client authentication?

No. One must have a value session before any RemoteObject functionality can be used.

connectDirect handles authentication too?.

Yes.

should our web client provide an authentication web page too?

Yes and no.

As noted above, we will need to do the OS-level authentication in the web interface from the server jetty.

But the P2J authentication is different and would still be handled as it is done today (inside connectDirect()). Please use a debugger to step through the connectDirect() process (that is the P2J client side, you will also have to step through the P2J server side which can be seen in RouterSessionManager.Incoming.run()). It is important that you understand how a P2J session gets setup. The authentication logic in the SecurityManager is quite complex because there are many different ways (batch vs interactive, certificate based auth, userid/password, plugins...) through. You should look at the simple server environment first, where you will see a kind of "null" authentication where we use a plugin to allow anonymous/guest access. But then you should also step through this process for a MAJIC client. That uses a real authentication process against user accounts in the server's directory and each successfully setup session has a specific P2J account context.

The interesting thing here is that we actually use a limited form of the 4GL UI processing to handle those cases (like MAJIC) that use interactive P2J authentication. During the connectDirect() we actually serialize an application-specific plugin class that implements the login UI and send it from the server to the client. It is executed there in our "early" P2J client, interacting with the user to get the userid/password details and then sending the results back to the server. SO: as long as our javascript client can handle our normal 4GL requirements, this support will "just work" without any changes. To see an example of this plugin code, see testing/majic/src/aero/timco/majic/security/LoginClient.java on devsrv01.

Because each handler is a unique instance the access to handle method is serialized.

Do you mean synchronized?

Even more on our case we have to take a decision before to send a response back to the user.
This means that we always have to wait for a client response in order to take the decision before sending the response back to the user.
On other words waiting on Spawner or waiting on WebHandler has the same effect, serialization access.
Due to this constraints I don't think that is possible to create web clients simultaneously.

I suspect we can solve this using:

  • thread pools so that there can be multiple incoming sessions at once
  • create a separate thread and reply immediately, unblocking the server, then use websockets between a javascript client and that new thread to handle the rest of the process
  • something else?

#50 Updated by Marius Gligor over 10 years ago

Here is a new design which is able to spawn the WebClient into the server context.

a) I designed a generic ClientBuilder class and a ClientBuilderParameters to store client parameters and avoid hard code values.

b) The client lunched was moved on WebHandler which is also a SpawnerListenr registered with the Spawner in order to receive a notifications.

c) The Spawner is used only to receive notification from the spawned process and to notify the local registered listeners about the remote server URI.

d) I assigned also a unique id (UUID) to each spawned process. When the process will notify the spawner the UUID and the URI are send over the network.
The Spawner listeners is a Map<UUID, Listener> and USING the remote UUID we are able to know precisely which listener should be notified.

e) The server KeyStore is exported via a RemoteObject interface not from the command line parameters.

2. In order to run the client on a specific user context (account) I think that we have a way to do that.

a) On Linux we have the command: su - &lt;user&gt; -c &lt;command&gt;.
b) On Windows we have the command: runas /user:&lt;user&gt; &lt;command&gt;

Both do the same, run the command using the <user> account.
Both asks for a user password.
Both seems to use the stdin to get the password.

The Process object in Java allow us to have control over stdin, stdout and stderr streams.
We can try to set the password by controling the stdin.

Also we have to change also the login scenario as follow.

a) The user open the browser and make a request to https://&lt;server_address&gt;:&lt;admin_port&gt;/chui
This is basically a HTTP/GET method. The response is a HTML page having a form which allow
user to send a user name and a password. This should be the user account on the machine
where the client should be spawned.

b) When the user press "Login" button the form is submitted. This is HTTP/POST request.

c) Using the user credentials we will try to spawn our client on the user account context.
If succeeded redirect the user to the page having the JS Terminal.
If error a page containing an error message will inform the user about the fails.

#51 Updated by Marius Gligor over 10 years ago

I started to do tests using Linux su and sudo commands.

1. For security reasons su and sudo doesn't allow stdin redirection.
I received the following error messages trying to use this commands with Java ProcessBuilder

su: must be run from a terminal
sudo: no tty present and no askpass program specified

2. As a root I'm able to use both commands. However in this case no ask for password is made!

Another drawback for su command:
Using: su - mag -c java                     // works fine
Using: su - mag -c java -version        // does not work.
we have to use something like this:
su - mag -c ~/script.sh
that is, we have to put the java command and arguments into a bash script and execute this script.

#52 Updated by Greg Shah over 10 years ago

The standard Java process launching does not properly setup the terminal environment. As mentioned before, the terminal environment is very important for the client processes to properly work. As you have found su and sudo won't work without it.

Please note that we do have our own process launching support that does setup the pty properly (see src/native/process.c). But this is only usable from the P2J client.

From a design perspective, we will want the solution to this problem to minimize deployment issues and shell/OS interface usage. This means we want to avoid scripts, avoid special permissions setup, avoid external configuration files, avoid dependence on utility programs and avoid shell processing. If these things can be avoided by writing code to native APIs in our JNI library, then that will be the preferred approach. Of course, at this point we need to find a solution that works at all. But I do know that this is possible (e.g. other programs like sshd are able to do this. It is probably a good idea to investigate OpenSSH Server to see the APIs and techniques they use.

The server KeyStore is exported via a RemoteObject interface not from the command line parameters.

I haven't gotten a chance to look at the posted code. But I think we can't use RemoteObject for transferring the keystore because in the normal case (non-guest access like in MAJIC) there won't be a valid session with the P2J server until the user has done an application login.

#53 Updated by Marius Gligor over 10 years ago

  • File spawn.c added

A. I did a lot of researches to find out a way to execute a command on Linux on the environment of other user,
Here are the results of my today work:

1. For security reasons it is obviously that a normal user cannot gain root permissions or access the
environment of other users directly. Only using sudo or su it is possible to do that.
The user MUST provide the root or the user password he otherwise cannot do sudo and su commands.
In other words only downgrade user permissions are allowed.
Even at low level using API calls it's not possible to do that as normal user.
2. Running at root (having root privileges) however it is possible to execute a command on the context of an existing user.
The steps to do that are:
- Having a user name and a password get the user informations (uid, password, etc.)
- If the user exists check if password match. Basically this is the authentication step.
- If user exists and the password match create a child process via fork(). The child process is still running as root.
On child process:
- Downgrade the child process permissions by changing the process uid to user uid. Set also the user environment.
- Execute the desire command.
On parent process:
- Wait for child process to exit.
3. The root mode execution is a MUST constraint.
4. The attached c source code is a simple implementation of this steps.
Of course this is a minimal implementation.
A real implementation could be more complex depending on the UNIX / Linux OS on which the application is running.
On my workstation was tested and found to work.

#54 Updated by Marius Gligor over 10 years ago

  • File deleted (spawn.c)

#55 Updated by Marius Gligor over 10 years ago

#56 Updated by Greg Shah over 10 years ago

This is good work. Some thoughts:

  • We will want to limit the scope of the process that is root. In particular, the main P2J server should NOT be run with root because any security flaw or breach will expose the entire system to problems. Instead, we will want to implement a very small "spawning" process that is run with root.
  • What is the controlling terminal status of the child process in this case?
  • The use of setuid() should be safe in that since the original context is root, there will be no "saved" uid context that can be swapped back in later.
  • I am curious to what extent this same behavior can be implemented on Windows. In particular, the process launching approach of fork() followed by exec() is not the design on Windows, so I expect some problems there.

#57 Updated by Marius Gligor over 10 years ago

Today I started to do tests using the spawn command written in C.
Here are the results.

I did some changes inside the C code. After the spawn application was build I did the following:

$sudo -s
$chown -v root:root spawn
$chmode -v 4755 spawn
Now the spawn command can be started by a normal user but run with root privileges!
I moved the spawn file into my ~/bin directory.
Due to my changes on the C code when spawn is used we should provide the entire path: ~/bin/spawn or /home/mag/bin/spawn
Next I created a new user on my system named bubu.
Than I tried:
mag@mag:~$ ~/bin/spawn mag &lt;mypassword&gt; ls /home/bubu
ls: cannot open directory /home/bubu: Permission denied
So far so good, I have no access on the bubu home directory.
Next I tried:
mag@mag:~$ ~/bin/spawn bubu &lt;bubu_password&gt; ls la /home/bubu  
total 24
drwxr-x--
2 bubu bubu 4096 nov 22 18:15 .
drwxr-xr-x 5 root root 4096 nov 22 17:16 ..
rw------ 1 bubu bubu 8 nov 22 17:17 .bash_history
rw-r--r- 1 bubu bubu 220 mar 30 2013 .bash_logout
rw-r--r- 1 bubu bubu 3637 mar 30 2013 .bashrc
rw-r--r- 1 bubu bubu 675 mar 30 2013 .profile
Works good, I have access on /home/bubu.
Next I tried to use spawn in the p2j to lunch the web client.
I added the following lines before java command on ClientBuilder.java:
...
command.add("/home/mag/bin/spawn");
command.add("bubu");
command.add(&lt;bubu_password&gt;);
// Build command
command.add("java");
...
I started the sever, I opened the Mozilla Firefox browser and I typed.
https://localhost:7443/chui
Unbelievable, It works!
I checked, if is real:
mag@mag:~$ ps -fu bubu
UID PID PPID C STIME TTY TIME CMD
bubu 20343 20341 99 17:54 ? 00:04:06 java -Xmx512m -XX:MaxPermSize=64m -Djava.library.path=/home/mag/p2j/build/lib -classp
mag@mag:~$ ps -fu root
root 20341 1 0 17:54 ? 00:00:00 /home/mag/bin/spawn bubu &lt;bubu_password&gt; java -Xmx512m -XX:MaxPermSize=64m -Djava.library.pa
Yes is real, it works!
1. However the password on the command line is in clear and this is not a good think.
Maybe en encryption is mandatory to hide the real password.
2. A big issue that I still not understand. My expectation was for java to require the -classpath to be set somewhere inside the bubu user environment.
In this case the p2j.jar and his dependencies must exists on each user home directory.
Surprisingly it works without this constraint, is seems that the spawned process is still able to access the jar files from my home directory (mag)!
3. I think that we can use this small tool in our project with minor changes regarding the password and potentially to limit the usage only for our purposes.
In this case I have to do some changes in Java cod to integrate the spawn command.
4. For Windows OS I have to do another research.

#58 Updated by Greg Shah over 10 years ago

This is really good work. Some thoughts:

1. Each spawned process must have a controlling terminal. You can use the who command to list the current terminals in use. Please look into this topic deeply (see these links) and make sure that the controlling terminal setup is correct for the resulting spawned process.

http://blog.nelhage.com/2009/12/a-brief-introduction-to-termios/
http://blog.nelhage.com/2009/12/a-brief-introduction-to-termios-termios3-and-stty/
http://blog.nelhage.com/2010/01/a-brief-introduction-to-termios-signaling-and-job-control/
http://uw714doc.sco.com/en/SDK_sysprog/_The_Controlling-Terminal_and_Pr.html
http://blog.nelhage.com/2011/02/changing-ctty/

Please also look at initConsole() in src/native/terminal_linux.c to see a bit about how we initialize the terminal settings at startup of the P2J ChUI client.

There is also this text from our P2J Runtime Installation, Configuration and Administration Guide:


Job Control and P2J Client

In environments where interactive shells provide job control, some special care should be taken when providing custom shell scripts that launch P2J clients. An example of such an environment is the bash shell on Linux. The job control feature makes it possible to stop the client process at an arbitrary (userspecified) time by typing CTRL-Z on the terminal. The operating system suspends the P2J client process and the bash shell regains control, which would differ from a typical Progress environment. Below is the explanation of when and why this may happen and how to avoid it.

When CTRL-Z is typed in the terminal, bash, being the parent of the client JVM process, receives a SIGTSTP signal from the tty driver in the kernel, if the controlling terminal is not in “raw” mode. The kernel sends this signal to all processes in the foreground process group in the bash session. bash itself is in the group.

Under normal conditions, the P2J client will have set the controlling terminal into raw mode. When in raw mode, the SIGTSTP signal is not sent and there is no problem. However, every time the JVM launches an interactive child process, the terminal is restored to the regular “cooked” mode. If the user presses CTRL-Z while interacting with this child process, the kernel will generate the SIGTSTP signal.
In response, the JVM safely ignores the signal. Unfortunately, bash still receives the signal and then performs a "service" for us. It reacts to the signal by sending the SIGSTOP signal to the current processes in the foreground job. This latter signal cannot be caught, blocked or ignored. Thanks to bash, the JVM process stops unconditionally and bash displays the command prompt.

Disengaging the JVM from the parent bash won't help, since bash would display the command prompt immediately and the user would be able to interact with the same terminal in which the JVM is operating. This would create a situation where there is concurrent use of the same terminal, quickly resulting in screen corruption or hangs.

The solution is not to allow bash receive the SIGTSTP. One of the two known ways is the TRAP instruction. Another, more efficient way is to use exec to invoke JVM process instead of eval. This causes the parent bash to go away leaving no parent of the JVM process which can process the job control signals.

This issue only affects the CHARVA based CHUI driver, since the swingbased driver does not run inside a terminal or shell environment.

2. The child process MUST NOT have ANY access to state or resources that are available to the parent process. The STDIO must be setup with its own controlling terminal. The current directory should not be the server's current directory. The environment should be fresh and should not have anything from the P2J server's environment. The fact that the p2j.jar was read from your home dir suggests that there is still some work in this area.

3. Please organize the design as follows:

  • Minimize what is in spawn.c to only those portions that are absolutely required.
  • Put everything else in the P2J code (java or the native code, as needed).
  • The P2J native code (accessed via JNI) can launch spawn when all other setup is done.
  • Perhaps you can write the password to STDOUT and spawn can read it from STDIN. This should be just as secure as other terminal login code in Linux.

#59 Updated by Marius Gligor over 10 years ago

New changes in the current design.

1. In p2j when the user open the following URL https://&lt;host&gt;:&lt;admin_port&gt;/chui a page having a login form is send as response for this request.
The user must enter an user name and a password and press the "Log in" button.
On the server if the user name and the password are valid values the request is redirected to the chui web client page, if not the login page
is returned together with an error message.

2. In the spawner.c the password is read from stdin not from the command line.
Inside the p2j the password is provided via stdin pipe. I've tested and I found that works fine.

On the spawner.c they are still a lot of works to do:
- I saw that the spawned process has no pseudo terminal assigned!
UID        PID  PPID  C STIME TTY       TIME CMD
bubu 14718 14716 99 18:59 ? 00:11:37 java -Xmx512m -XX:MaxPermSize=6 ...
- The child process should be detached from his parent process.
In this case the p2j design should be slightly changed to provide paths inside the user account home directory for class path and native libraries path.
Each user should have the p2j client on his space. Where should be located the p2j client on each user?
I'm going to investigate this issues by reading the links posted by you and other articles related to this topics.

3. When I spawned Java process fro another account first I got the following message:

Can't connect to X11 window server using ':0' as the value of the DISPLAY variable.
Do you ever seen such a message? Is this cause by the pty missing?
I've fixed this issue by providing the following JVM argument -Djava.awt.headless=true

#60 Updated by Greg Shah over 10 years ago

In this case the p2j design should be slightly changed to provide paths inside the user account home directory for class path and native libraries path.

It is unlikely that each user will have their own copy of P2J. No one would really want that because it would be a nightmare to maintain. And we can't have that version be different code than the code in the server's P2J jar since that can cause problems with Java serialization and our RemoteObject protocol.

The common case will be that the customer will have a shared installation somewhere on the system. I think it is acceptable to have this be part of the configuration in the server's directory. It should be possible to have this point to any valid paths on the system, but it can be shared for all spawned clients. Make sure to provide configuration options for both the classpath and the native library path.

Can't connect to X11 window server using ':0' as the value of the DISPLAY variable.

If I remember properly, this is caused when running a new session using the root account. The X server will fail to initialize by default because running X in a root account opens too many security holes. But our client should never be run until after the root account is dropped.

I don't know if we will be able to run headless or not. One problem will be if our use of any of the swing/AWT classes is allowed. Of course, we can eliminate that dependency by using some new classes to encode colors and rework our hierarchy. It may cause other problems too. The client we run must be as functional as any ChUI client that is run interactively.

#61 Updated by Marius Gligor over 10 years ago

Today I finished to design and implements a spawner tool working like "su" command but allow us to specify the user password by redirecting the stdin.

- In the current implementation the users rights are restricted to their rights on the system.
For example the normal user "foo" has no rights to read the "mag" files on "mag" home directory.
As a result the Java code should be adapted to this behaviour by specifying shared locations for classpath and native libraries path.
Also we have to assign a working directory for our client. The spawner set as working directory the user home directory.
If you have a proposal regarding this topic please let me know.

- I fixed the issue regarding the controlling terminal. When the spawn starts a child process is created.
The child process create a new session and became a session leader (PPID=1).
A session leader is decoupled from his parent and the parent can exit without to wait for child exit.
However a session leader has no controlling terminal.
Next a new pseudo terminal (pts) is assigned to session leader process.
A new child process is created to execute the command. The session leader than wait for his child to exit.
Bellow is an example of a spawned client for user "mag":

UID PID PPID C STIME TTY TIME CMD
mag 7728 1 0 17:46 pts/10 00:00:00 /home/mag/bin/spawn mag java -Xmx512m -XX:MaxPermSize=64m -Djava.awt.headless=true -D
mag 7729 7728 99 17:46 pts/10 00:00:16 java -Xmx512m -XX:MaxPermSize=64m -Djava.awt.headless=true -Djava.library.path=/home/

mag 6876 1 0 17:34 ? 00:00:03 /usr/bin/konsole
mag 7075 6876 0 17:38 pts/5 00:00:00 /bin/bash
mag 7227 7075 0 17:39 pts/5 00:00:00 mc
mag 7229 7227 0 17:39 pts/9 00:00:00 bash -rcfile .bashrc
mag 7650 7229 0 17:46 pts/9 00:00:00 /bin/bash ./server.sh
mag 7651 7650 8 17:46 pts/9 00:00:02 java -XX:MaxPermSize=256m -Xmx1024m -server -classpath ../../../build/lib/p2jadmin.ja

- I created also a simple makefile to build the spawn tool.

- Another question please. On a secure mode both net:server:port and net:server:secure_port should be provided?

The next step is to adapt the Java code on P2J to define and use shared directories in order to spawn chui clients
other users than the user which runs the server.

#62 Updated by Marius Gligor over 10 years ago

Add GCC flags on makefile for a RELEASE build not a DEBUG one.

#63 Updated by Marius Gligor over 10 years ago

Makefile improvements.

#64 Updated by Greg Shah over 10 years ago

This is really good work. It seems very close to what we need. Some feedback:

1. There are a few coding convention issues and mis-spellings (e.g. chek_root() should probably be check_root()).

2. Setting the current dir to the home directory is the correct thing to do as the default. However, it will be very important to allow this to be overridden via configuration in the server's directory.

3. Please integrate the build of this into the primary p2j makefile. If you want to maintain a separate makefile, that is OK, but perhaps it needs to be renamed to makefile.spawn to make it clear it is dedicated for spawn. Both spawn.c and the makefile.spawn should be placed in src/native/ and build when the rest of the native p2j code is built.

4. Since we are adding some tricky permissions processing into our P2J installation/configuration dependencies, I think it is very important and every possible error condition in spawn.c be given its own unique error code. For example, we should be able to tell if setgid() failed or if setegid() failed or if... I know this will cause the code to be expanded, but the future benefits to our admins will be well worth it. Create a set of constant definitions like this:

...
#define  ERR_SPAWN_SETGID_FAILURE   -10
#define  ERR_SPAWN_SETEGID_FAILURE   -11
...

The errors should start at -1 and all be negative numbers.

5. About your question:

On a secure mode both net:server:port and net:server:secure_port should be provided?

You can see how we deal with this in LeafSessionManager.connectDirect(). Basically, if net:connection:secure is true, then we expect there to be a net:server:secure_port specified and we do NOT read or otherwise use the net:server:port.

#65 Updated by Marius Gligor over 10 years ago

1. Spawner is now integrated with p2j project.

- I did minor changes into spawn.c to manage (close) the file descriptors properly.
- I provided separate exit codes on each API calls.
- On build.xml I created 3 new targets:
spawn         // build spawn
spawn_root // build spawn and apply chown and chmode
spawn_clean // delete spawn build.
Both spawn_root and spawn_clean require root password to operate properly.
Basically spawn_root should be execute for a complete build.
I added the spawn_root target to the dependencies list of native target.
When the native target is executed prior the spawn_root target is executed which ask for root password.
If you want other behaviour please let me know.

2. I did changes on the p2j project in order to adapt the code for per client environment spawn.
The configuration vlaues are read from server directory. Bellow are my new directory settings:

<node class="container" name="chuiWeb">
<node class="boolean" name="enabled">
<node-attribute name="value" value="TRUE"/>
</node>
<node class="boolean" name="secure">
<node-attribute name="value" value="FALSE"/>
</node>
<node class="string" name="target">
<node-attribute name="value" value="/chui"/>
</node>
<node class="string" name="spawner">
<node-attribute name="value" value="/usr/share/p2j/spawn"/>
</node>
<node class="string" name="jvmArgs">
<node-attribute name="value" value="-Xmx512m -XX:MaxPermSize=64m -Djava.awt.headless=true"/>
</node>
<node class="string" name="classpath">
<node-attribute name="value" value="/usr/share/p2j/lib/p2j.jar"/>
</node>
<node class="string" name="libPath">
<node-attribute name="value" value="/usr/share/p2j/lib"/>
</node>
<node class="string" name="workingDir">
<node-attribute name="value" value="./"/>
</node>
<node class="string" name="configFile">
<node-attribute name="value" value="config.xml"/>
</node>
<node class="integer" name="rows">
<node-attribute name="value" value="24"/>
</node>
<node class="integer" name="columns">
<node-attribute name="value" value="80"/>
</node>
<node class="string" name="background">
<node-attribute name="value" value="0x000000"/>
</node>
<node class="string" name="foreground">
<node-attribute name="value" value="0xFFA500"/>
</node>
<node class="string" name="selection">
<node-attribute name="value" value="0x0000FF"/>
</node>
<node class="string" name="fontname">
<node-attribute name="value" value="monospaced"/>
</node>
<node class="integer" name="fontsize">
<node-attribute name="value" value="12"/>
</node>
</node>

I used /usr/share/p2j as a location to shared spawn, native library, p2j.jar and dependencies.
Users should have rights to read files and scan directory from this location.
When the user open https://&amp;lt;host&amp;gt;:&amp;lt;admin_port&amp;gt;/chui from a web browser a page having an authentication form is send as response.
The target root /chui is also configurable. Is this OK or is best to remain hard codded?
The user should enter his account credentials (user name and password) an press the "Log in" button.
If the credentials match the user account a new web client is spawn using his account environment and the
original request is redirected to a new URI, the "chui web client" JS WebSocket application.
On any error the login page having an error message is returned and the user could try again.
This spawner is build on Linux OS and my expectations are to work properly on almost all Linux distributions with few or no changes.
I'm not sure how it works on Unix OS like Solaris because I have no conditions here to do tests.
Mac OS is also build on top of an Unix kernel, but I think that nobody is using a Mac machine as a server.
For Windows OS we have to think to a solution. I will try to see if runas command supports stdin redirection for password.
Usually the Windows Servers OS are more restrictive on security aspects than an trivial Windows Workstation OS.
So a best idea is to finally test the Windows spawn solution on a Windows Server OS.
Anyway this constraints are for the OS on which the p2j server is running. The user could use any OS since the application
is a web one and he need only a web browser Chrome, Firefox, Safari, Opera or IE.
To use only a Linux server to run the p2j server could be a solution?
Do you have clients that require Windows OS as server host?

#66 Updated by Greg Shah over 10 years ago

I will do the code review next. First, I wanted to get some basic answers to you.

When the user open https://&lt;host&gt;:&lt;admin_port&gt;/chui from a web browser a page having an authentication form is send as response. The target root /chui is also configurable. Is this OK or is best to remain hard codded?

It is fine to be hard coded. This will simplify the configuration and documentation. There is no good reason to change the URL so hard coding is OK.

To use only a Linux server to run the p2j server could be a solution?

This is not possible.

Do you have clients that require Windows OS as server host?

Yes, Windows OS support is required.

#67 Updated by Marius Gligor over 10 years ago

1. I did some minor changes on the current implementation:
- The target root /chui is now hard coded.
- On the spawner I did a change to fix the set-up of the working directory for spawned process.

2. For Windows OS using "runas" command is useless, like using "su" command on Linux.
However I found Win API calls which allow us to design and implements a small tool that looks like Linux spawn.
Basically the tool will have the same arguments on the command line and the behaviour will be the same obviously on Windows environment.
Currently I'm working to design and implements the spawn tool for Windows OS.
Until the end of the day I will send you a first release of this tool.

#68 Updated by Greg Shah over 10 years ago

Please fix these two javadoc errors as part of your update:

  [javadoc] /home/ges/projects/p2j/src/com/goldencode/p2j/ui/client/chui/driver/swing/ChuiSimulator.java:79: warning - Tag @link: reference not found: ChuiApplet
  [javadoc] /home/ges/projects/p2j/src/com/goldencode/p2j/ui/client/chui/driver/swing/ChuiSimulator.java:579: warning - @param argument "opt" is not a parameter name.

#69 Updated by Marius Gligor over 10 years ago

Fixed javadoc errors on ChuiSimulator.java

#70 Updated by Marius Gligor over 10 years ago

This is the first Windows OS version of our spawner. Its looks and works like the Linux spawner.
I'm using Microsoft Visual C++ 2010 Express Edition and Microsoft Windows 7 SDK to create and build this solution.
I tested on my laptop having a Windows 7 OS and found to work properly.

Useful informations:

- Users MUST have a password, blank password is not allowed.
- According to MSDN this API's are available since _WIN32_WINNT as 0x0500 (Windows 2000).
- Due to some privileges requirements we can improve this version by combining CreateProcessAsUser call
with CreateProcessWithLogonW call as MSDN recommended (see bellow). I will try to do that.

MSDN:
Windows XP with SP2 and Windows Server 2003: You cannot call CreateProcessWithLogonW from a process that is running under the "LocalSystem" account, because the function uses the logon SID in the caller token, and the token for the "LocalSystem" account does not contain this SID. As an alternative, use the CreateProcessAsUser and LogonUser functions.
To compile an application that uses this function, define _WIN32_WINNT as 0x0500 or later.

MSDN:
Typically, the process that calls the CreateProcessAsUser function must have the SE_INCREASE_QUOTA_NAME privilege and may require the SE_ASSIGNPRIMARYTOKEN_NAME privilege if the token is not assignable. If this function fails with ERROR_PRIVILEGE_NOT_HELD (1314), use the CreateProcessWithLogonW function instead. CreateProcessWithLogonW requires no special privileges, but the specified user account must be allowed to log on interactively. Generally, it is best to use CreateProcessWithLogonW to create a process with alternate credentials.

#71 Updated by Greg Shah over 10 years ago

Code Review 1202b

This is really good work. Some feedback:

1. Please look at src/com/goldencode/p2j/util/EnvironmentOps.getOperatingSystem() and src/com/goldencode/util/PlatformHelper.java. I prefer to enhance one of these instead of implementing another approach to OS detection like Os.java. The EnvironmentOps approach is 4GL compatible, so it is probably more correct to modify PlatformHelper if necessary. Today, we simply treat things as Windows and non-Windows (which are effectively all UNIX/Linux). This will work fine for all platforms that we anticipate working with in the near future. Let me know what changes might be needed. I want to make sure we don't break the current functionality.

2. Instead of calling the native build of spawn from build.xml, I prefer if you call it from src/native/makefile. I am also attaching some recent changes that I have made to build.xml and makefile. This won't work without additional files that I am editing, but it will give you can idea of the changes that are in play here. You will see that I have moved much of the OS detection and other complexity from build.xml to makefile, in an effort to centralize the native stuff and make it as independent as possible. It is straightforward to call the spawn build from the main makefile.

3. Please consider how we can configure things to allow the builds to be automated. You can edit the sudoers file and setup a particular command to be executed as root without any password prompting. The idea here is that we need to ensure that we can enable things like our automated regression testing or continuous integration servers to build without needing a human to authorize the chown/chmod processing.

4. Does the Linux/UNIX spawn approach integrate with the PAM ("Pluggable Authentication Modules") infrastructure? I assume it does, but we should know one way or the other.

5. Please change the spawn.c to have if(, switch( and check_user ( to be coded as if (, switch ( and check_user(.

6. When the authentication fails (AUTH_ERR_*), shouldn't we return a specific code via exit() instead of writing using puts()? Otherwise, how will the user (at the web browser) see the error?

7. In comments and javadoc, "lunch" or "Lunch" should be "launch"/"Launch". ;)

8. Can we easily crypt the plain text password in Java (using J2SE features) that is compatible with the Linux/UNIX crypt? The idea is to do this as soon as the input is made via the POST so that we don't store the plain text in memory. Then we can remove the crypting in spawn.c and never have to worry about the plain text being intercepted. In a perfect world it would alreayd be crypted in javascript before sending it over the wire to the P2J server, but that may be too much work to do quickly.

9. I wonder if some of the settings in ClientBuilderOptions should not be static. For example, in the common case it is likely that the working directory will not be something that is shared between users. Of course, the most common case is to have the working dir be set to the home dir, but I can see other cases too. Please consider which of these may be useful to split up on a per user basis and/or how to deal with this issue.

10. In ClientBuilder, the use of System.out.println() should probably be replaced with proper logging. I can see this as being useful long term (but only when verbose logging is requested).

11. Let's default to not having the client.xml bootstrap file. It is not required generally and we want to avoid using it unless absolutely necessary. It looks to me like we always try use it today and warn when it is not around. It should be rare to need it. In fact, I would prefer to add directory options to allow the customer to put all the needed settings in the directory (and we would just construct the proper command line with all those options), eliminating the need for the bootstrap config. For now, leave it as a possibility but if it is not specified in the directory, don't try to add it.

12. Please rename onRemoteServerListen() to embeddedWebClientIsReady().

13. I think SpawnerImpl needs to be properly synchronized.

14. I'd like to see the error codes from spawn failures (and appropriate error text messages) propagate back to the error page returned by WebHandler. At a minimum, the AUTH_ERR_* issues must be shown to the user. But I think it makes sense to show all errors rather than forcing the admin to look at some other location.

15. We still have the "chicken and egg" problem I mentioned before. This current code only works because the simple server environment is configured for guest access. As soon as a normal P2J authentication process is required, the user won't be able to access the login screen from the web browser, because during connectDirect(), the client's embedded web server is not yet started (AND the server's key store has not yet been obtained AND the remote URI has not yet been returned back to the server's WebHandler). Today, the connectDirect() is able to establish a session without any user interaction because of the special guest access authentication plugin that is in use in your testing environment. That is not a normal configuration. The trick here: how do we move the key store and URI transfer to happen BEFORE the connectDirect() AND still keep strong security/privacy?

#72 Updated by Greg Shah over 10 years ago

About the Windows approach and winspawn.zip:

1. Please do not use the MS VisualStudio or Visual C++ tools. It would bring too many proprietary dependencies to our project. For Windows development, we use mingw. This is an open source, Windows port of gcc and the other common build tools that we use on UNIX/Linux (the linker, make...) that is packaged in a manner that allows full WIN32 development. This is not using the cygwin compatibility environment at runtime, instead we would link to the msvcrt to get the basic C runtime (this DLL is part of the OS so it is OK).

To install:

1. Take one of the full archive: mingw-w32-bin-i686-2013MMDD.7z or mingw-w64-bin-x86_64-2013MMDD.7z. Unpack to \mingw32 or \mingw64.
2. Include the appropriate \mingwXX\bin to the PATH variable. In my systems: C:\mingw64\bin and C:\mingw32\bin depending on Windows architecture.
3. The 64-bit binaries are found at http://www.drangon.org/mingw. The 32-bit binaries and much more info can be found at http://mingw.org.

Please look at the makefile I posted to see the windows specifics. You can make some minor changes and have the same makefile.spawn used for both.

2. Generally, try to use common code between spawn for Linux and spawn for Windows. Where needed, use OS APIs. Where possible use C runtime features that are common. See our src/native/ directory for how we implement platform specific helper files that are called from common/shared source files.

Otherwise, I think you are on the right track.

#73 Updated by Marius Gligor over 10 years ago

1. In think that Windows and non-Windows (which are effectively all UNIX/Linux) detection is enough. Os.java has been deleted.

2. TODO. OK I have to do that after the implementation of both spawn projects for Linux and Windows is done.

3. Indeed we can configure sudoers to allow particular command to be executed without any password prompting.
In this case the sudoers file should be edited "by hand" on each workstation where the build is running.
We cannot do that automatically. We can only show how to do that and each member of the team should follow our instructions.
Is this what you are looking for?

4. The Linux/UNIX spawn approach is not integrated with the PAM ("Pluggable Authentication Modules") infrastructure, but will be soon.
Currently I'm working to add PAM authentication support to spawn.c
Using PAM is more portable over Linux/Unix platforms.

5. Done

6. Done. Among error messages an error code is already returned from the main method (see exit(result)).

7. Done.

8. I'm afraid isn't possible to do that on pure Java. Also the PAM implementation require the password unencrypted.
Even more as you can see the crypt API require the clear password and the encrypted password.
crypt(password, correct); The encrypted password contains a "salt" used on encryption according to Linux documentations.
I found also that the algorithm is not the same on all Linux platforms.

9. The working directory could be absolute or relative. With absolute paths the users shares the same working directory.
With relative paths each user has it own working directory inside its home directory.
Another issue here. I tried to access directory services when I prepared to spawn a client. It doesn't work!
According to my debugs something like directory is not bound cause the error.

Location of this error is on DirectoryService.java line 4770, the return value is null, see bellow.
protected Remapper synchBatch(String id, boolean rw)
{
if (!isBound())
return null;

The only way to read the directory values was at the moment of StandardServer start-up.
Do you have any idea how to fix that?

10. Done.

11. Done.

12. Done.

13. On SpawnerImpl I'm using a ConcurrentHashMap which doesn't require synchronization.

14. Done with some remarks. When we launch the spawner we have control only over the spawner process.
The spawner will exit after the first fork() and the return code from child processes are no longer under our control.
I can get only the exit code from the spawn process not for it child's.
In conclusion I implemented this feature only for spawner process exit codes (AUTH_ERR_*).

15. I understood. Here we need to use a secure interprocess communication mechanism. Could be JMX, Java RMI, JMS or maybe another solution.
At this moment I have no idea about a best solution. I have to do some "researches" to find a solution.

#74 Updated by Greg Shah over 10 years ago

3. Indeed we can configure sudoers to allow particular command to be executed without any password prompting.
In this case the sudoers file should be edited "by hand" on each workstation where the build is running.
We cannot do that automatically. We can only show how to do that and each member of the team should follow our instructions.
Is this what you are looking for?

Yes, almost. The only additional issue I see is that we don't want to have chown and chmod in the sudoers file as the command. The reason: it is a big security hole to open to solve a small problem like this.

Instead, please create a shell script that handles the exact chown/chmod requirements. Then as part of the instructions, make sure that the ownership and permissions on that shell script are set very tight (maximal limitations).

The only way to read the directory values was at the moment of StandardServer start-up.
Do you have any idea how to fix that?

You have to have a security context defined for any thread that accesses the directory. On such a thread, the following code will provide access:

      DirectoryService ds = DirectoryService.getInstance();

      if (!ds.bind())
         throw new RuntimeException("directory bind failed");

You can see code like that at the top of StandardServer.standardEntry() which is the primary server-side entry point for new client sessions to start running 4GL code.

You may also want to look at the code in StandardServer.bootstrap() which is how the server initializes. That involves creating the directory service and the security manager instances. Notice how we don't read anything from the directory before the security manager is operational.

The WebServer instance is created and initialized on that same bootstrap thread. So reading the directory is OK there. But when the parent class (GenericWebServer) is instantiated, it calls startup() which creates a thread pool for handling requests. These handler threads don't have such a context and cannot access the directory.

For now, can we read the directory during initialization and pass the data into the web server? I prefer not to add security context to the web server handler threads if not necessary, to limit the security risk that may happen if those threads are breached in an attack. Worst case, we will open up some context as needed.

I can get only the exit code from the spawn process not for it child's.
In conclusion I implemented this feature only for spawner process exit codes (AUTH_ERR_*).

We will need to ensure that the child process' return code is written somewhere for logging/diagnosis purposes.

15. I understood. Here we need to use a secure interprocess communication mechanism. Could be JMX, Java RMI, JMS or maybe another solution.
At this moment I have no idea about a best solution. I have to do some "researches" to find a solution.

One way might be to use a pipe. We could open that in the spawner and inherit it in the child. The key store can be read from it and the URI can be written back to it. This has the advantage of being easily accessible from native code and very portable.

A better solution is to implement a temporary P2J session with the server. The idea: create one-time authentication credentials (some kind of random token/key) that can be passed (like our password info) via STDIO to the child process. If we can find a secure way to get it to the P2J client, the P2J client can use this to do a temporary connectDirect() to the server with those credentials, read the key store, initialize the web server and send back the URI. Then it would disconnect and drop into the normal processing. The major advantage of this approach is that it can be extended to the "remote client" scenario, which we will be implementing later. Another major advantage is that we plan to do something similar to allow batch mode P2J clients to be autostarted by the server.

Constantin: do we have anything already made for appserver that can help us here? How do the new appserver "clients" authenticate?

#75 Updated by Marius Gligor over 10 years ago

1. Today I succeeded to build the spawner for Windows OS using MinGW compiler.
MinGW does no support main functions for UNICODE applications (_tmain, wmain). This is a well known issue among MinGW Windows developers.
UNICODE is mandatory on spawner because the function CreateProcessWithLogonW is available only for wide characters not for ANSI characters.
Fortunately using a main function wrapper I fixed this issue.

2. I added support for PAM on the Linux version of spawner.
As I told you on Linux version I can get back in Java only the exit code for the master process.
My idea is to use the Linux syslog to audit the execution of child processes.
Please let me know if you agree this solution and I will do the appropriate changes.

3. I started to design a makefile for both Linux and Windows builds.
On Windows we have to use the MinGW compiler and I need references to include and lib folders of MinGW.
I created an environment variable MINGW_HOME instead to set the reference in the makefile.
Also on MinGW the name of the maker is mingw32-make and on Linux gcc is only make.
So far I created 2 ant targets but I have to find if is possible to create properties for maker
depending on platform and finally to have a single ant target for build on Linux and Windows.

#76 Updated by Greg Shah over 10 years ago

My idea is to use the Linux syslog to audit the execution of child processes.
Please let me know if you agree this solution and I will do the appropriate changes.

That is OK.

On Windows we have to use the MinGW compiler and I need references to include and lib folders of MinGW.
I created an environment variable MINGW_HOME instead to set the reference in the makefile.

That is strange. I didn't find the same requirement in src/native/makefile. What is different in the spawn case?

Also on MinGW the name of the maker is mingw32-make and on Linux gcc is only make.

Make sure you are not using the automated installer for mingw. I initially had the same problem but when I "manually" unzipped the archives, everything was named properly (make was make).

So far I created 2 ant targets but I have to find if is possible to create properties for maker
depending on platform and finally to have a single ant target for build on Linux and Windows.

Please call the processing of the spawn makefile from the P2J src/native/makefile instead of from ant.

#77 Updated by Greg Shah over 10 years ago

I guess I should point out that our ant build.xml and src/native/makefile already works for mingw on 32-bit Windows, so you should not have to make too many changes. There must be something different about your environment, from the environment on which we normally run.

Eugenie: do you have any ideas?

#78 Updated by Eugenie Lyzenko over 10 years ago

Eugenie: do you have any ideas?

I think the best way is to take precompiled binaries(e.g from here: http://code.google.com/p/mingw-w64-dgn/). Then unzip to some location and include path to the BIN subdirectory in Windows PATH variable.

It should work. No other changes required. And "make" will be "make".

The recent binaries are: mingw-w32|64-bin-i686|x86_64-20131018.7z. This works for both 32 and 64 bit Windows(Running inside KVM on Ubuntu).

#79 Updated by Marius Gligor over 10 years ago

1. I downloaded and copied the MinGW binaries for both Win 32 and Win 64 bit platforms as follow:

c:\mingw32  (32 bit binaries)
c:\mingw64 (64 bit binaries)

In PATH I added c:\mingw64\bin Now the maker is make.exe and this is OK.

When the winspawn.c is build following references are needed:

For compile the following include files:
Windows.h
WinBase.h
Userenv.h
Shlwapi.h

For link the following libraries:
libuserenv.a
libshlwapi.a

These references are available from:

c:\mingw32\i686-w64-mingw32\include 
c:\mingw32\i686-w64-mingw32\lib
and
c:\mingw64\x86_64-w64-mingw32\include 
c:\mingw64\x86_64-w64-mingw32\lib

To solve this issue I used 2 environment variables:

MINGW32_HOME=c:\mingw32\i686-w64-mingw32
MINGW64_HOME=c:\mingw64\x86_64-w64-mingw32

Inside makefile.spawner I used a place holder MINGW_HOME and depending on detected platform MINGW32_HOME or MINGW64_HOME is assigned to MINGW_HOME.
I did the build on both 32 and 64 platform and it's works.

Do you agree this solution? If not please let me know your proposals.

2. To run the build I created a new target in makefile which is a recursive use of make to build spawn from makefile.spawn
The spawner is build when the native classes are build using ant native target. No ant target for spawn build are available as you suggested.

However I found a little problem here.
If the JRE_HOME variable is located in a directory having spaces in name like JRE_HOME=C:\Program Files\Java\jdk1.7.0_21 the ant native build
fails because the space character between "Program" and "Files" is interpreted as a separator when the INCLUDES is build using foreach (see bellow):

INCLUDEDIRS=${JRE_HOME}/../include ${JRE_HOME}/../include/win32 $(SRCDIR)
override INCLUDES+=$(foreach d,$(INCLUDEDIRS),-I$(d))

One solution is to use JRE_HOME on a path without spaces like: JRE_HOME=C:\jdk1.7.0_21 and everything works.

#80 Updated by Greg Shah over 10 years ago

libuserenv.a
libshlwapi.a

What are these?

As a general rule, I prefer to dynamically link to DLLs that are part of the base OS rather than using static linking, but the decision can be made on a case by case basis.

Do you agree this solution? If not please let me know your proposals.

Yes, I agree.

If the JRE_HOME variable is located in a directory having spaces in name like JRE_HOME=C:\Program Files\Java\jdk1.7.0_21 the ant native build
fails because the space character between "Program" and "Files" is interpreted as a separator when the INCLUDES is build using foreach (see bellow):

Please rework this makefile code to eliminate this problem. I don't want to force more environment dependencies when we can just use a different technique.

#81 Updated by Eugenie Lyzenko over 10 years ago

For point 1:

Are you building both 64 bit and 32 bit targets on only 64-bit Windows system?

#82 Updated by Marius Gligor over 10 years ago

I have a Windows 7 64bit OS installed on my laptop.
Using ant native target the 64bit platform is detected and the spawner is build using MinGW 64bit tools only.
No 32bit build is done if detected platform is 64bit.
No 64bit build is done if detected platform is 32bit

I did the 32bit build just for testing purposes using a separate makefile without platform detection.

#83 Updated by Marius Gligor over 10 years ago

1. For Linux spawner I added PAM support and syslog audit.
Basically the spawner can be build with or without PAM support depending if PAM is defined or not in the build script.
By default the PAM is defined and spawn is PAW aware. To check you could do that:

ldd spawn | grep libpam

to see if spwan depends on libpam.

2. To build the spawner for Linux and Windows a single makefile target is used.
On the makefile for build java native libraries I added a new target spawn which in turn will start the spawner make build script from makefile.spawn
When the native artifacts are build the spawn artifact is also build. No ant targets are used to build the spawner.
I have a postbuild target defined in the makefile.spawn script used to do the sudo commands on Linux/Unix.
On Windows postbuild target is used only to copy the spawn.exe file to /build/lib folder.
In Linux postbuild target run a bash script postbuild.sh which should be added to sudoers in order to skip enter the sudo password.
How to add to sudoers is described inside the postbuild.sh script file.
Unfortunately I cannot test the build script for Solaris OS because I have no such OS installed on my machines.
Searching on Internet I found that Solaris does not have sudo command but could be installed.
Could you help me here with some suggestions?

3. Regarding the space characters in file path I didn't found any solution.
All that I found so far says to avoid using space characters in file names and paths when working with GNU make.
GNU make does not support spaces in file names or paths.

#84 Updated by Marius Gligor over 10 years ago

I changed the winspawn.c to use dynamic DLL link instead a static link.
As a result of this changes the makefile build script has been simplified. The reference MINGW_HOME is no longer needed.
The MAX_PATH (260) is not enough to hold the command line and has been increased.
I done also a minor improvement in postbuild.sh

#85 Updated by Marius Gligor over 10 years ago

Today I started top test the spawner on Windows OS environment and I found some interesting issues.

1. The font Monospaced is available in both Linux and Windows OS. However ClientDriver in Windwos does not work using this font.
After a short debug I found the cause. At one moment in ChuiSimulator line 627 a check on font is done to be monospaced.
The test is performed by the code from SwingHelper#isMonospaced as follow:

if (!font.canDisplay('\u2500'))
return false;
// check if this font is monospaced, this is a bit of a hack but should
// be reliable
int msize = fm.charWidth('m');
int isize = fm.charWidth('i');
int osize = fm.charWidth('1');
int nsize = fm.charWidth('9');
int dsize = fm.charWidth('\u2500');
return (msize  isize && msize  nsize &&
msize osize && osize dsize);
According to my tests the following values are returned for Monospaced font size 12:
Linux      Windows
msize = 7 7
isize = 7 7
osize = 7 7
nsize = 7 7
dsize = 7 12
As you can see the dsize which is the size of character use to draw boxes is different
in Linux and in Windows. As a result in Windows the function return false and the font
is not recognised as monospaced by the ClientDriver
Here I have an ideea how to fix this issue and I want to ask you if is OK to do that:
if (!font.canDisplay('\u2500'))
return false;
// check if this font is monospaced, this is a bit of a hack but should
// be reliable
int msize = fm.charWidth('m');
int isize = fm.charWidth('i');
int osize = fm.charWidth('1');
int nsize = fm.charWidth('9');
int dsize = fm.charWidth('\u2500');
int gsize = font.getSize();
return (msize  isize && msize  nsize &&
msize osize && (osize dsize || fsize == dsize));
In other words we can consider also the case on which the size of the box character is equals to font size. 
I tested this code on Windows and its works.

2. A second issue related to fonts in Windows is when the font name contains spaces.
For example I used the font "Lucida Sans Typewriter" 12 which works on Windows.

Windows
msize = 7
isize = 7
osize = 7
nsize = 7
dsize = 7
Here the name should contains quotes in command line.
Finally I found the solution and I already fixed this issue on Java and now its works.

#86 Updated by Greg Shah over 10 years ago

In other words we can consider also the case on which the size of the box character is equals to font size.
I tested this code on Windows and its works.

I am fine with the change.

#87 Updated by Greg Shah over 10 years ago

Code Review 1206a

The code is moving forward nicely. Keep up the good work.

#88 Updated by Marius Gligor over 10 years ago

I tested the Windows spawner and found to work properly.
I fixed also some issues related to fonts, monospace fonts detection and space characters in font names.

#89 Updated by Marius Gligor over 10 years ago

1. The archive contains some small improvements. I extended the use of quoted string on file name and path strings as well.

2. I understand that we cannot use the p2j RemoteObject to export the server KeyStore as currently is implemented
an we have to do that earlier before the p2j authentication when RemoteObject is not available.
So I did a research to find a cross platform solution for server KeyStore imports / exports. Bellow are my conclusions:

a). Named pipes are available on both Linux/Unix and Windows platforms.
However Java does not provide support for named pipes.
The implementation of named pipes is different on Linux and Windows platforms.
In Linux named pipes are implemented as files but on Windows not.
On Windows it's possible to create and use named pipes only by using Win API.
In order to use named pipes a JNI solution should be designed and implemented.

b). The best solution remains the use of a network socket solution.
This solution is cross platform, portable, less error prone and easier to implements.
On this area I have 2 proposals:

- First proposal is to use the Hessian Binary Web Service Protocol (http://hessian.caucho.com/) which is easily
to integrate with p2j jetty web server (just one jar file added to dependencies), its works like a RMI or RPC solution
having support for serialization but over http/https protocols.
Hessian implementation is released under an open source license (the Apache license).
- The second solution is to use a Jetty WebSocket's solution to do almost the same job.
In this case we have also to design an application protocol (OSI layer 7) used to manage the conversations
between client and server.
Both socket solutions allow local/remote access, are portable over platforms, and are secured due to Web Server secured environment on which they runs.
For simplicity I prefer the first solution Hessian Hessian Binary Web Service Protocol.

Please let me know your opinions regarding this issue.

#90 Updated by Greg Shah over 10 years ago

Before I get into a deep review of these new options, what about my preferred solution as documented in note 74?

A better solution is to implement a temporary P2J session with the server. The idea: create one-time authentication credentials (some kind of random token/key) that can be passed (like our password info) via STDIO to the child process. If we can find a secure way to get it to the P2J client, the P2J client can use this to do a temporary connectDirect() to the server with those credentials, read the key store, initialize the web server and send back the URI. Then it would disconnect and drop into the normal processing. The major advantage of this approach is that it can be extended to the "remote client" scenario, which we will be implementing later. Another major advantage is that we plan to do something similar to allow batch mode P2J clients to be autostarted by the server.

#91 Updated by Marius Gligor over 10 years ago

OK. Where I can find such an example in our p2j project?

#92 Updated by Greg Shah over 10 years ago

Eric has done some work with creating an authentication token for remote database access. Perhaps that can help.

The authentication token/certificate should be 1-time use only. It must have a large enough random hash that it has enough entropy that it cannot be reasonably broken by any kind of brute force attack.

Constantin: I'll repeat the previous question:

do we have anything already made for appserver that can help us here? How do the new appserver "clients" authenticate?

Today we also already have an infrastructure for certificate based authentication. If we can enhance that to make the certificates a one-time use thing (generated on the fly?), then the rest of the solution should be an effort in properly configuring the client and server and then calling the connectDirect().

#93 Updated by Eric Faulhaber over 10 years ago

See com.goldencode.util.RandomWordGenerator and its use in com.goldencode.persist.DatabaseManager.createEmbeddedProperties(Database).

#94 Updated by Constantin Asofiei over 10 years ago

do we have anything already made for appserver that can help us here? How do the new appserver "clients" authenticate?

There was nothing special added for the authentication of appserver agents; but, the configuration of an agent requires it to have a passwordless login (i.e. certificate, trusted or something else).

#95 Updated by Eric Faulhaber over 10 years ago

Eric Faulhaber wrote:

See com.goldencode.util.RandomWordGenerator and its use in com.goldencode.persist.DatabaseManager.createEmbeddedProperties(Database).

Please note the performance-related implementation note in RandomWordGenerator.fillRandomSlots. If you need to generate very long words frequently, the algorithm can be improved by decrementing the len value by 1 each time through the loop and walking the array to the generated index, skipping slots already full (also will need to account for elements already in the array from previous passes when determining initial value of len). Not terribly efficient, but possibly better than calling Math.random many times unnecessarily. Another approach might be to add a level of indirection using an additional array to track used indexes in the first array. Another is to simply advance to the next open slot if the slot at the generated index is full, though we lose some randomness with this approach. I'm sure there are other approaches as well...

Anyway, I didn't need this extra efficiency/complexity for the initial use case, which is called infrequently and only generates words of 128 characters, so I didn't complicate the initial implementation.

#96 Updated by Greg Shah over 10 years ago

You can also look on devsrv01 at the ~/testing/majic/run/batch/ directory to find examples of batch client configurations that can be used with run/client/client.sh when you also use the -b option. Of course, these are each also configured already in the server's directory. The trick in this case is to dynamically create all this configuration in a manner that can only ever be used once.

I do prefer the use of a real certificate over an auth token, as it should enable full use of the rest of our batch process support without any changes to the authentication process.

#97 Updated by Constantin Asofiei over 10 years ago

Eric, about optimizing the RandomWordGenerator.fillRandomSlots - take a look at this approach:
- each possible slot in the buffer (from 0 to buffer.length - 1) is added to a list, L. This is passed to each call of fillRandomSlots.
- when determining which slot is next to be assigned, we generate a random number R between 0 and L.size() - 1. The next slot in the buffer will be determined by the value of element at position R in list L.
- after R is determined, we remove the element at position R from list L. Thus, list L will decrease in size each time a buffer slot is assigned and there will be at most buffer.length random calls (for the buffer slot computation).

#98 Updated by Eric Faulhaber over 10 years ago

This is similar to the additional array approach I noted, but I agree a list (specifically linked list) is a better implementation choice, given the removal idea. Thanks for the details.

The current approach is not a bottleneck at all if it is not being called constantly for very large strings, so we should only complicate the implementation if there is a performance need.

#99 Updated by Greg Shah over 10 years ago

Code Review 1209a

I am fine with the changes.

#100 Updated by Marius Gligor over 10 years ago

Today I started to learn about SecurityManager and SessionManager which are complex technologies.
So far I investigated the SessionManager#connectDirect client side flow and I hove some questions:

Doing debugs on the flow finally I found the TransportSecurity constructor class.
Here on the Java doc I found that on the client side a combination of truststore and keystore is used to create an in memory KeyStore.
Looking on Majic client start-up configurations both a truststore and a keystore are used on almost all cases.

1. Could you explain me in a few words what kind of information is stored in this files?
2. Is any server dependency inside this information data?
3. On the client side processalias and useralias are used as aliases on client truststore and keystore resources.
On the server side inside directory I found 2 containers users and processes.
Is any relation between the directory containers and aliases? What are the differences between users and processes regarding authentication?

4. When no truststore and keystore are used on client side an anonymous (guest) authentication is considered.
Currently this guest account is defined inside server directory users container and is mapped to user "bogus".
As I understood the guest account is not secured and could be invalidate is this true?

5. On SecurityManager#authenticateClientWorker I found that multiple authentication modes are available.

AUTH_MODE_X509
AUTH_MODE_IDPW
AUTH_MODE_X509_IDPW
AUTH_MODE_CUSTOM
Each kind of authentication mode has specific use.
AUTH_MODE_X509 - using c X509 certificate
AUTH_MODE_IDPW - user name and password
AUTH_MODE_CUSTOM - seems to be the case of using a custom plug-in like you told me you already use on Majic.
The authentication mode is established by server is this true or could be requested by client?
Where is specified the authentication mode inside server?

6. The TransportSecurity is a singleton as is described by the javadoc. We cannot not call SessionManager#connectDirect twice using 2 different configurations.
Maybe some changes should be made if we will need to use 2 calls. What do you think about this?

7. It is possible to dynamic generate a KeyStore and a X509 certificate self signed at any moment. It is not possible to design such objects for single use.
The expiration date is the single element that is used to validate / invalidate the certificate.
Using an expiration date closest to the issue date a certificate could be used for a short time but not only once.

8. It's still not clear for me how to implements your requests.
So far my idea is to do the following:

On client side:
a) Generate truststore and keystore dynamically on the client side.
b) Try to connect to the server and create a session via a bootstrap configuration containing the generated truststore and keystore and other specific settings.
The authentication mode could be AUTH_MODE_X509.
c) On success, import server key store, start the embedded server and notify back on server about the URL on which the embedded server listen.
d) Close the connection (session) with the server. This was just a temporary session used only for our purposes.
e) Continue the flow, create a new session with server using the default configuration, etc.
...
On server side:
I don't know yet but I think that as a minimum an account having X509 certificate authentication mode should be defined.
Here I have to do more investigations.

Please help me with any suggestions.

#101 Updated by Marius Gligor over 10 years ago

Inside directory.xml I just found:

<node class="authMode" name="auth-mode">
<node-attribute name="mode" value="4"/>
<node-attribute name="retries" value="0"/>
<node-attribute name="plugin" value="com.goldencode.p2j.security.GuestAccess"/>
</node>
<node class="container" name="auth-plugins">
<node class="container" name="guest_login">
<node class="string" name="classname">
<node-attribute name="value" value="com.goldencode.p2j.security.GuestAccess"/>
</node>
<node class="string" name="description">
<node-attribute name="value" value="Non-Interactive Guest Auto-Login"/>
</node>
<node class="string" name="option">
<node-attribute name="value" value="bogus"/>
</node>
</node>

Something similar I found inside Majic server directory.xml
The question here is:

Only a single auth-mode and a single plugin can be defined per server instance?
If yes we cannot create a temporary session for server key store import because the same plugin is always present on all sessions.

#102 Updated by Greg Shah over 10 years ago

1. Could you explain me in a few words what kind of information is stored in this files?
2. Is any server dependency inside this information data?

For details, please see the "Key-Stores and Trust-Stores" chapter of the "Progress 4GL to Java (P2J) Installation, Configuration and Administration Guide".

3. On the client side processalias and useralias are used as aliases on client truststore and keystore resources.
On the server side inside directory I found 2 containers users and processes.
Is any relation between the directory containers and aliases?

Yes, in an indirect manner. In the directory, account information for user accounts is kept in the users container and process accounts in the processes container. The useralias is how the client code finds the certificate to represent a user account and the processalias is how it finds the cert for a process account.

What are the differences between users and processes regarding authentication?

It is an unfortunate design decision that I should have eliminated early in the project. The bottom line: a process account is essentially one that is not directly associated with a specific end-user, but is instead usually representing some non-interactive process like a:

  • converted 4GL "batch" program
  • remote Java application that is connecting to the P2J server to make remote object calls

The authentication mode is established by server is this true

Yes.

or could be requested by client?

The client must be trying to use one of the modes that are supported by the server. Otherwise this would be a security hole.

Where is specified the authentication mode inside server?

It is configured in the directory (as you have recently found).

6. The TransportSecurity is a singleton as is described by the javadoc. We cannot not call SessionManager#connectDirect twice using 2 different configurations.

As far as I know it is not a singleton. Where do you see this?

7. It is possible to dynamic generate a KeyStore and a X509 certificate self signed at any moment. It is not possible to design such objects for single use.
The expiration date is the single element that is used to validate / invalidate the certificate.
Using an expiration date closest to the issue date a certificate could be used for a short time but not only once.

An idea here would be to remove the certificate once it has been used one time. In other words, if the SecurityManager no longer honors that cert after it has been used once, then it doesn't matter that the expiration time is not over yet.

Please help me with any suggestions.

http://security.stackexchange.com/questions/14000/environment-variable-accessibility-in-linux

On modern systems, it seems that the process' environment is reasonably secure (only accessible by root and by other processes of that same euid). Can we do the following:

  1. On the server, generate a one-time use userid and password that is random enough and big enough to be proof against brute force attacks.
  2. On the server, the security manager would have to be aware of this special account (it would have to be enabled and then disable itself after being used once).
  3. The server would pass this information to the spawn executable.
  4. The spawn executable would insert this into the environment and the spawned child process can read it there.
  5. The client will use this to establish the temporary session.
  6. In the temporary session, the keystore can be read, the embedded web server will be initialized and the URI will be written back.
  7. The client will disconnect the temporary session and continue with the normal ClientCore processing.
  8. The server will redirect the web browser to that URI.

This avoids the complexity of the certificate processing. We could even pass an encoded keystore down to the P2J client through the environment BUT I don't see a way to get the URI back, so I think some kind of temporary session is still needed.

#103 Updated by Greg Shah over 10 years ago

Only a single auth-mode and a single plugin can be defined per server instance?

No, we allow different auth modes as a default, server-specific default, group-default or account level.

#104 Updated by Marius Gligor over 10 years ago

OK Thanks. I will try to do that.
Regarding the singleton instance of TransportSecurity please read the javadoc for SecurityManager#getTransportSecurity at line 6908

#105 Updated by Greg Shah over 10 years ago

Regarding the singleton instance of TransportSecurity please read the javadoc for SecurityManager#getTransportSecurity at line 6908

I see the text now. It is inaccurate. Please fix it as part of this work.

#106 Updated by Marius Gligor over 10 years ago

A more portable solution is to send the random generated user-id and password directly as arguments in command line.
To be more secured we could also encrypt the values for this arguments.

#107 Updated by Greg Shah over 10 years ago

The problem is that on Linux/UNIX, command line args are visible to anyone. Using encryption would require both sides know a particular key in order to decrypt the values. That suggests we would have to hard code the key in our code (which is not secure) or we would have to otherwise provide the key as part of configuration (which is the very problem we're trying to solve in the first place).

#108 Updated by Marius Gligor over 10 years ago

Today I started to implement the direct connection using a strong random subject id and password passed as environment variables.
Using RandomWordGenerator I generated strong values for both subject id and password.
The values are used to create and add two environment variables using ProcessBuilder#environment interface.
The values are converted to hex strings on server side and converted back to the original values on client side.
I added this conversion steps in order to avoid any problems caused by special characters generated by RandomWordGenerator.

Next I tested on both Linux and Windows OS and I found to works fine. On client now I'm able to get the values with no problems.

On the next step I want to implement the server side code and I have some questions:
I found the AdminServerImpl class in admin package which export a lot of administrative operations.
I saw that is used by the admin console applet.

1. I would like to use this helper class to create and delete temporary users. Is this OK?

2. When we create a new user (account) we have to provide an UserDef structure.
I think that I have to create a protected user (with password) and a custom authentication mode.
I have to specify also a custom authentication plug-in.
Should I design and implement a new custom login plug-in for this purpose?

3. In P2J and Majic I found the following entries registered inside directory

P2J (directory.xml):
<node class="authMode" name="auth-mode">
<node-attribute name="mode" value="4"/>
<node-attribute name="retries" value="0"/>
<node-attribute name="plugin" value="com.goldencode.p2j.security.GuestAccess"/>
</node>

Majic (gso-directory.xml):
<node class="authMode" name="auth-mode">
<node-attribute name="mode" value="4"/>
<node-attribute name="retries" value="-1"/>
<node-attribute name="plugin" value="aero.timco.majic.security.Login"/>
</node>

Now if we have to add a new plug-in for the same mode how we can do that?

#109 Updated by Greg Shah over 10 years ago

1. I would like to use this helper class to create and delete temporary users. Is this OK?

Yes, this should be OK.

You will have to meet some requirements for this to work:

  • The thread on which you make these calls must have a proper security context AND that context's account must include sufficient permissions to execute all of the actions required.
  • You will have to honor the control flow that would be followed by a "real" admin console user. In particular, you will have to force a "refresh" of the security cache after adding/removing a user.

It may make sense to create multiple accounts, in advance, one time. The idea is to permanently leave these accounts in the system. Then you can just:

  1. set the password and enable an account from the pool when you are launching a new client
  2. disable that account when the logon has occurred

The only drawback to this approach is that the overall number of simultaneous logons will be limited by the size of this pool. However, in practice, the actual amount of time needed to use this mechanism during the client spawning process will be very small. I suspect that even a relatively small pool will scale pretty well.

a custom authentication mode.
I have to specify also a custom authentication plug-in.
Should I design and implement a new custom login plug-in for this purpose?

I'm not completely sure of this. The permissions of what can be called by this account can be very "locked-down". We can limit this to just the right to call a specific exported remote object interface. Then the client just uses "normal" userid/password authentication mode, obtains the proper remote object, calls that remote object and then disconnects. On the server side, the call to that remote object should be sufficient to notify the server to disable the account and when the logoff occurs, the account must go back into the pool.

There may be some minor changes needed in the security package to enable this, but I don't see the need for a custom auth plugin.

Now if we have to add a new plug-in for the same mode how we can do that?

This specifies a single global DEFAULT auth-mode (and default plugin).

But you can override this at the group account and user account levels (and maybe the server level too, I'm not sure).

#110 Updated by Marius Gligor over 10 years ago

On StandardServer start-up the AdminExports interface is registered:

ifaces = new Class[] { AdminExports.class };
RemoteObject.registerStaticNetworkServer(ifaces, AdminServerImpl.class);

On the AdminLogon after a successful logon a remote reference to this interface is obtained and used via a connectDirect call.
I tried to do the same but so far with no success. I'm using the admin account credentials.
The problem is that so far I cannot create a connection to the server using admin credentials and get the AdminExports remote interface.
Is any default session available to get the interface reference without connectDirect?

Is this the right way that we could follow, using such a connection to get access to the AdminExports interface?

#111 Updated by Greg Shah over 10 years ago

Is this the right way that we could follow, using such a connection to get access to the AdminExports interface?

No.

There is no reason for a session or to be using the remote object interface when the code that is running is in the same JVM process. I think this is as simple as calling AdminServerImpl.addUser(), right? You just need to do it from a P2J server thread that has the proper security context.

In addition, please try my idea of creating a pool of pre-existing accounts. This will greatly reduce the effort for each new session AND it should be much faster too. The creation of this pool can be a 1-time thing (if the pool doesn't exist, it gets created) that is persistent (the accounts aren't ever deleted).

#112 Updated by Marius Gligor over 10 years ago

I tried to create a temporary account when the StandardSever start-up using AdminServerImpl.addUser() but with no success.
I think that StandardSever runs on P2J server thread because I can read from directory on this thread.
The problem is coming from here:

// access the target
WorkArea wa = workArea.get();
if (!wa.targeted || !wa.sa.adminAccess()) {
return false;
}

wa.targeted is false and wa.sa is null.

#113 Updated by Marius Gligor over 10 years ago

I added AdminServerImpl.setTargetLive(true) to solve the previous issue but now It said that I have no admin rights. SystemResource#checkAdminAccess return false.
return systemAccessWorker("admin", SYSTEM_ACCESS);

#114 Updated by Constantin Asofiei over 10 years ago

I think you should bypass AdminServerImpl entirely and just use SecurityAdmin to add a new user.

I added AdminServerImpl.setTargetLive(true) to solve the previous issue but now It said that I have no admin rights.

As this is done using the security context of the server process (i.e. standard for the test directory.xml), you need to make sure that the server process account has the appropriate ACLs; check the ACLs for the "admin" account and "admins" group - I think it could work just by adding "standard" account to the "admins" group.

Greg, about the pool of "generated, pre-existing, accounts": wouldn't adding them to the "live" security cache generation expose them to the Admin Console? I think we will need some separate management for this pool, and not just add them to the global user list.

#115 Updated by Greg Shah over 10 years ago

wouldn't adding them to the "live" security cache generation expose them to the Admin Console

Yes, but that is probably OK. We can create them with verbose descriptions that make it clear these are internal/web accounts. Admins can be trained to leave these alone.

I think we will need some separate management for this pool, and not just add them to the global user list.

I worry that creating "shadow" users would require major changes throughout our security package. I want to keep this as simple as possible. These accounts will normally be disabled anyway, except during the moment we are trying to spawn a client.

#116 Updated by Marius Gligor over 10 years ago

I'm using SecurityAdmin instead AdminServerImpl but unfortunately the problem remains the same.
On the server directory I have for groups:

<node class="container" name="groups">
<node class="group" name="everybody">
<node-attribute name="description" value="all users"/>
</node>
<node class="group" name="admins">
<node-attribute name="description" value="all admins"/>
</node>
</node>

The standard is defined as process account.

<node class="container" name="processes">
<node class="process" name="standard">
<node-attribute name="enabled" value="TRUE"/>
<node-attribute name="description" value="P2J server"/>
<node-attribute name="master" value="TRUE"/>
<node-attribute name="server" value="TRUE"/>
<node-attribute name="alias" value="standard"/>
</node>
</node>

I tried to add the standard account to admin group without success.

&lt;node-attribute name="groups" value="admins"/&gt;

This kind of node is not allowed.

#117 Updated by Marius Gligor over 10 years ago

I fixed the issue by adding the process standard to the subject list in ACL (see bellow)
Now I'm able to create user accounts from StandardServer thread.

<node class="container" name="acl">
<node class="container" name="system">
<node class="container" name="000100">
<node class="resource" name="resource-instance">
<node-attribute name="reference" value="admin"/>
<node-attribute name="reftype" value="TRUE"/>
</node>
<node class="systemRights" name="rights">
<node-attribute name="check" value="true"/>
</node>
<node class="strings" name="subjects">
<node-attribute name="values" value="admins"/>
<node-attribute name="values" value="standard"/>
</node>
</node>

#118 Updated by Marius Gligor over 10 years ago

I started to design and implements the temporary account pool. Finally I was able to create user accounts from StandardServer thread.
I added the "standard" account to the ACL list of subjects in directory.xml for "admin" rights.

- I designed a temporary account pool using a FIFO queue (ConcurrentLinkedQueue) to hold temporary accounts.

- When the pool is created on start-up also a group "temps" is created if the group does not already exists. All temporary accounts created and added to the pool belongs to this group.

- Also the temporary accounts are created if necessary to fill the entire pool. The size of the pool is configured form directory.

- When a temporary account is requested from pool the item form the head of the pool is returned or null if the pool is empty. The item is removed from pool.

- When the item is no longer needed is returned to pool, enqueued at the tail of pool and reused.

This design works fine. However I found a lot of constraints and I want to ask you about a possible solution.

1. The pool is created from the StandardServer thread on start-up when we have enough rights to manipulate user accounts.
Unfortunately after the initial creation it is no longer possible to manipulate user accounts from other threads (jetty).
As a consequence we cannot enable/disable accounts on "the fly". Basically we cannot do any changes to accounts.
Also in my mind was to create new accounts whenever the pool became empty instead to return a null value but this is also not possible.
Do you known any idea on how we could fix that?

2. I saw that the user name is stored always in lower case regarding that the generated string contains upper case characters.
This is just an observation because have no side effects in my implementation.

3. Regarding the password I sow that using the same password for two different users one created form admin console the other
created by me with SecurityAdmin API have different encrypted format!!! Inside the code the decrypted values are OK.
However using the account created from console manager allow me to authenticate remote but using my generated accounts does not.
Do you have any hint for me here? Anyway on the next step I want to do deep investigations on this issue.

#119 Updated by Greg Shah over 10 years ago

1. The pool is created from the StandardServer thread on start-up when we have enough rights to manipulate user accounts.
Unfortunately after the initial creation it is no longer possible to manipulate user accounts from other threads (jetty).
As a consequence we cannot enable/disable accounts on "the fly". Basically we cannot do any changes to accounts.

I think we should create a daemon thread that can handle such tasks. That thread would have to be setup with the proper security context when it is started. In fact, the initial setup of the accounts and so forth can be done there too.

Then it is a matter of establishing the appropriate queueing or IPC mechanism between jetty and this daemon thread. This ensures that the jetty thread doesn't need special permissions.

3. Regarding the password I sow that using the same password for two different users one created form admin console the other created by me with SecurityAdmin API have different encrypted format!!!

This is a surprise to me as well.

#120 Updated by Marius Gligor over 10 years ago

In the mean time I found why the password format are different. AdminConsole does not store the clear password encrypted.
Instead first a SHA hash is created using the clear password bytes using HashPassword.hashPassword tool.
The generated hash is encoded and stored in directory as password. This make the password secured, cannot be decrypted ever!
The problem is that we have to store the password like AdminConsole and send the clear password encrypted as environment variable.
On the client side we have to provide the clear password for authentication when we create a server direct connection session.
Because we cannot decrypt the password we have to keep the encoded password on another parameter in user account. Maybe the owner field would be used.
Or another solution should be found.

#121 Updated by Marius Gligor over 10 years ago

Also the Base64 encryption/decryption algorithm is used. Basically isn't a strong encryption.

#122 Updated by Marius Gligor over 10 years ago

I have an idea. We can use a random string constant like a "salt" and the password should be constructed
from user name also random generated plus the "salt" combined using a specific function.
We have to send remote only the user name and on the client side using the same "salt" and algorithm the password is reconstructed.

#123 Updated by Marius Gligor over 10 years ago

Another solution is to delete all accounts from temps group on each server start-up and create new accounts.
The passwords for the new accounts are kept in memory and sent remote. This approach seems to be more secured.

#124 Updated by Greg Shah over 10 years ago

The problem is that we have to store the password like AdminConsole and send the clear password encrypted as environment variable.
On the client side we have to provide the clear password for authentication when we create a server direct connection session.

This is OK.

These accounts will be disabled except during the moment of use. As long as the account has its password changed to a newly created random text just before it is enabled, then it is OK to send the clear version of this down to the client. Only someone with the rights to run a debugger or otherwise inspect the kernel data structures/proc file system can see this information. It is OK.

Also the Base64 encryption/decryption algorithm is used. Basically isn't a strong encryption.

Indeed. It is obscuring the data only and can be reversed so it isn't encryption at all.

We can use a random string constant like a "salt" and the password should be constructed from user name also random generated plus the "salt" combined using a specific function. We have to send remote only the user name and on the client side using the same "salt" and algorithm the password is reconstructed.

We don't need to do to this level. The real security it adds is not enough because anyone with the salt value can still get the password.

Another solution is to delete all accounts from temps group on each server start-up and create new accounts.
The passwords for the new accounts are kept in memory and sent remote. This approach seems to be more secured.

I like this. We still need to disable/enable accounts only when they need to be used, but leaving this data out of the persistent directory does seem cleaner.

#125 Updated by Marius Gligor over 10 years ago

I implemented the temporary account pool by deleting all temporary accounts and create new ones on server start-up.
Now it's works fine but without enabled/disable account at this moment.
I will try to fix later this issue because now I have another big issue to fix and I need some helps.

When the ClientDriver is started using chui_native or swing_chui_frame a terminal window is open ant the users are working inside this TTY window.
As I understood that on Majic application an authentication plug-in is used and the user should enter his credentials before start working on the p4g converted application.

In our case however the flaw is different. The web login page is used to authenticate the user against OS.
On success the user is redirected to a web page containing a JS TTY emulator.
No other console windows are open for user. Even more the user should be located somewhere remote on another machine.
In this case the authentication for p4g converted application should be done using JS TTY emulator.
Is this true?

Here I have some remarks and questions:

When I tried first to open a server session using a temporary account always the GuestAccess plug-in is used.
Using this plug-in Regarding the user name and password for temporary account the authentication succeed.
The user is always authenticate using bogus account as is specified inside directory.
I tried to set custom plug-ins to temporary accounts when I created temporary users but it does not work.
It seems the auth-mode is global and unique.

<node class="authMode" name="auth-mode">
<node-attribute name="mode" value="4"/>
<node-attribute name="retries" value="0"/>
<node-attribute name="plugin" value="com.goldencode.p2j.security.GuestAccess"/>
</node>

1. How we can fix this issue?

2. If we change this plug-in how the ClientDriver will work with other screen drivers?

3. Using chui_web screen driver the user authentication should be done using the JS TTY window?
If YES should ClientDriver still have an open session?

#126 Updated by Greg Shah over 10 years ago

In this case the authentication for p4g converted application should be done using JS TTY emulator.
Is this true?

Yes. Of course, we haven't gotten that written yet, but so long as the JS driver works like any other driver, then it will naturally allow the client to operate just like the swing or native client.

1. How we can fix this issue?

I think the problem comes from not knowing the userid until after connectDirect() is called. I think this can be solved using some settings in the bootstrap configuration. Please look at SecurityManager.sendAuthType() which is called from SecurityManager.authenticateClientWorker() which is the main client-side authentication processing method.

2. If we change this plug-in how the ClientDriver will work with other screen drivers?

There is no need to change the plugin.

3. Using chui_web screen driver the user authentication should be done using the JS TTY window?
If YES should ClientDriver still have an open session?

Yes. You are writing a "JS driver" right? It is a kind of plugin that provides UI input and output. In the JS driver case, the driver itself is really split into 2 pieces. The "top half" is written in java and runs in the P2J client JVM process. The "bottom half" is written in javascript and runs in the web browser. But this is no different than the other ChUI drivers that we have. None of them know anything about the authentication process. BUT they all get used by the authentication process when an interactive login is needed.

I think you may need to look at the SecurityManager.authenticateLocal() which is the server-side of the interactive login. Note there that in CUSTOM mode we serialize an Authenticator class and send the class' bytes to the client side, where it is loaded and executed as a special piece of UI client code. That code uses the UI services of the driver (whichever one is there) to display a UI to the user and obtain userid/password credentials. Then it packages these credentials and sends them back to the server. The driver has no idea this has anything to do with login. The driver is just acting like its normal UI service.

To see a real example of this, please look at the code in testing/majic/src/aero/timco/majic/security/LoginClient.java. A simpler version can be found in src/com/goldencode/p2j/security/DefaultLoginPanel.java.

There is no problem to solve here that won't be automatically solved by just properly implementing the JS driver.

#127 Updated by Marius Gligor over 10 years ago

OK. I fixed the authentication issue using security:authentication:type=program like AdminLogon do.
Using an interactive type security:authentication:type=user an authentication plug-in is mandatory.
Remains to fix the issue related to enable/disable temporary accounts using a thread having an appropriate security context.

Regarding JS TTY during the last weekends I exercised my JS skills to create a prototype.
The result is here http://www.gmax.ws/tty.html
It's just an exercise but I think that looks good, isn't it?

#128 Updated by Greg Shah over 10 years ago

Code Review 1213a

The changes continue to move in the right direction. Some minor feedback:

1. I prefer to have a flag at the top of ClientCore.start() named "web". You can set this true if the uuid is in the bootstrap config. If it is set true, only then would you call setWebClientConfig() and startUp(). This allows the reader to understand that these are conditional, without having to read "into" those methods. This is similar to how we have the "dbg" flag.

2. Please rename setWebClientConfig() to addWebClientConfig().

3. Please rename startUp() to initializeWebServer().

4. In WebClientSpawner, I think we may need more than 5 seconds of timeout on systems with a heavy load.

5. In WebClientSpawner, I prefer to wait a reasonable amount of time for an account to become available instead of throwing an exception.

#129 Updated by Greg Shah over 10 years ago

The result is here http://www.gmax.ws/tty.html
It's just an exercise but I think that looks good, isn't it?

Very cool!!! Well done.

Yes, I am really excited to see us get to work on that part.

#130 Updated by Marius Gligor over 10 years ago

I fixed the enable/disable temporary account using the AssociatedThread class.
Bellow is a short description of temporary accounts cash implementation.

1. On the server start-up all temporary accounts are deleted (accounts belong to temps group)

2. New temporary accounts are created and enqueued to pool. The created accounts are created disabled.
The number of initial temporary accounts is defined in registry.

3. When an item is requested from pool the status of the related account is changed to enabled.

4. When an item is returned to pool the status of related account is change to be disabled.

5. On heavy loaded systems when the pool potentially became empty a new temporary account is created and returned.
This kind of accounts are created enabled and served immediately to caller.
Later when the items are returned to pool the related accounts will be disabled.
Basically all requests for temporary accounts are honoured.

#131 Updated by Constantin Asofiei over 10 years ago

Marius Gligor wrote:

5. On heavy loaded systems when the pool potentially became empty a new temporary account is created and returned.
This kind of accounts are created enabled and served immediately to caller.
Later when the items are returned to pool the related accounts will be disabled.
Basically all requests for temporary accounts are honoured.

A quick thought: is it possible for the queue size to get out of control? As I understand, this step is before any user authentication is done, thus someone could build a script to send a massive amount of requests, thus OOME-ing the server... I think a static pool size is better; you can limit the amount of time to wait until an account can be retrieved from the pool (this can be made configurable too).

#132 Updated by Marius Gligor over 10 years ago

OK. I started to change the design to have a fixed pool of accounts.

#133 Updated by Marius Gligor over 10 years ago

New design. Fixed size for temporary accounts pool.

#134 Updated by Marius Gligor over 10 years ago

Add code to terminate the thread on server shut down some minor changes.

#135 Updated by Greg Shah over 10 years ago

Code Review 1217a

1. if(web) in ClientCore should be if (web).

2. In TemporaryAccountPool, the import of LogHelper should be using the wildcard.

3. I don't understand the purpose of this code in TemporaryAccountPool.createUser():

         if (user != null && user.enabled == enabled)
         {
            account = new TemporaryAccount(user.subjectId, password);
         }

4. I prefer if you would rename TemporaryAccountPool.createInstance() back to getInstance(). I think it better describes what is being done AND it is more consistent with other singleton classes in P2J (like SecurityManager...).

5. In TemporaryAccountPool.poll(), there may be a logic problem. In the item == null (pool empty) case, it seems that if an account is found before the timeout expires, the account is returned back to the caller without being enabled.

Shouldn't this code:

            // Change user status
            Runnable core = new UpdateAccountTask(item.getSubject(), true);
            TemporaryAccountPool.poolTask.execute(core);

execute no matter how the "item" is found?

6. I would like you to make a change to the build process to bypass the execution of postbuild.sh by default. In other words, for the default build, no postbuild.sh would get run. This is the most common case (where people don't need that to run) and I don't want to make all users force a new sudoers configuration change or a requirement to enter the password. If you can make this something that can be added optionally from the command line, that may be best.

#136 Updated by Greg Shah over 10 years ago

After #2201, you will next be working on #2212. I will add more details/guidance tomorrow. If you run out of work before then, you can start by analyzing the kinds of communication flows that will be needed to support the ChUI driver. Make sure you consider the needs of both directions of flow. Document your findings/thoughts here.

#137 Updated by Constantin Asofiei over 10 years ago

Review for 1217a (beside Greg's notes):
  • in makefile.spawn, you are using tabs for indenting. More, in postbuild.sh, spawn.c and winspawn.c, the indenting is done at 4 chars, not 3.
  • ChuiWebDriver.startupServer is missing javadoc
  • WebHandler - the header is indented one-space to the right...
  • WebHandler.handle/doGet/doPost - check the formatting of the method definition
  • Clientcore.initializeWebServer - you are checking for the driver to be a ChuiWebDriver instance. Isn't this somehow restricting? I think there should be a WebDriver interface, to mark the web drivers.
  • For the new added classes, the class javadoc is minimal. Please improve the javadoc to explain what these classes do and how they should be used (this will be helpful later on, when we will write documentation for all of this).
  • TemporaryAccountPool.createUser - I think the certificate alias for the new user should be null, not hard-coded to shared.
  • in UpdateAccountTask.run, we are calling AdminServerImpl.targetRefresh();. This is a very expensive operation (as it generates a new security cache generation on each call) and I'm not sure how this will behave in high-load moments, when a high number of users are authenticating simultaneously.

#138 Updated by Greg Shah over 10 years ago

in UpdateAccountTask.run, we are calling AdminServerImpl.targetRefresh();. This is a very expensive operation (as it generates a new security cache generation on each call) and I'm not sure how this will behave in high-load moments, when a high number of users are authenticating simultaneously.

This is a very good point. The security generations will grow with no bound in this case AND it can have performance implications.

Please look into ways that we can solve this:

1. dynamically edit all existing security cache(s) in which the temp accounts exist, without using a refresh or creating a new generation
2. make the security and admin code aware of the concept of truly temporary account that is not backed by the directory (automatically goes away at server shutdown) and is kept separate from the generations of security cache, allowing these accounts to be managed as a single pool that is not subject to the refresh requirements

I prefer option 2 if it is reasonable to implement.

#139 Updated by Marius Gligor over 10 years ago

According to my tests without a "refresh" the account changes (enable/disable) are not "visible" and when the client try
to do a remote authentication an error "account is disabled" is throw and the authentication fails.

On the other hand the account pool is mandatory because we need to keep the clear password in memory.
This means that we have always to generate this accounts on server start-up when we know the clear passwords.
The pool is used just to serve the account subject and a clear password used for authentication.
The authentication process it self has nothings to do with our pool.

In my opinion authentication is done relative rarely and isn't used intensively by users.
That's why we assert that a small size pool should works fine.
Anyway I'll try to find a solution as you suggested.

#140 Updated by Greg Shah over 10 years ago

In my opinion authentication is done relative rarely and isn't used intensively by users.

In comparison to the runtime of a session, yes. In other words, each session is commonly expected to be long-lived. But on any given day there will be hundreds or even thousands of logins to a particular server instance. Server instances that are left running for months will have a very large number of security generations that may start to cause problems.

That's why we assert that a small size pool should works fine.

The small pool is OK so long as there are not too many simultaneous logins. Except for companies that have shift operations where a large number of users login within a short interval, a small pool will work OK.

But that doesn't mean that this processing can't have an effect. When the security cache is refreshed, it is a costly activity in the security code. It will affect the performance of all other ongoing security operations, such as ACL checks. This isn't only about login processing.

According to my tests without a "refresh" the account changes (enable/disable) are not "visible" and when the client try to do a remote authentication an error "account is disabled" is throw and the authentication fails.

Yes, this is expected and correct.

There is one other thing, that I should have noticed before. Please change the password every time you re-enable the account. I don't want the passwords to be re-used. The accounts themselves can be reused. But otherwise, someone that breaks a password can just keep trying it in a tight loop while spawning new sessions and eventually they will get in. It is perfectly possible to login using the same account more than 1 time simultaneously. That is by design because our users must be able to have multiple sessions. But it exposes a security hole in the scenario where we re-use passwords.

#141 Updated by Marius Gligor over 10 years ago

I've fixed almost all the issues reported on your code reviews with some remarks:

1. In TemporaryAccountPool, the import of LogHelper should be using the wildcard. This issue cannot be fixed due to conflict with another class
from package com.goldencode.util.LogHelper and I need the class from this package com.goldencode.p2j.util.LogHelper

2. I introduced a command line parameter to execute the postbuild.sh script
The parameter should be use as follow:
a.) ant native
will run build native artifacts without running postbuilt target on makefile.spawn

b.) ant -Dpost.build=yes native
will run build native artifacts and execute also the postbuilt target on makefile.spawn
In other words the parameter "post.build" should be provided in command line and his value should be "yes".
-Dpost.build=yes

3. Checking ChuiScreenDriver issue. In order to be more generic I created a new interface WebScreenDriver that should be implemented by other web screen drivers in the future.

4. Related to AdminServerImpl.targetRefresh(). Whithout the refresh the enable/disable account is not visible and
the remote authentication fails due to "account is disbaled" error.
Greg suggested me some possible fixes. I have to do a research for a possible solution.
So far I found that a list of user accounts is stored inside the SecurityCache.
I think that doing a short work around the SecurityManager and SecurityCache it is possible
to add a method to update (refresh) a user account inside the SecurityCache instead to refresh the entire context.
We have to search on the list of accounts stored inside the SecurityCache instance and update only one account.
Do you think that this scenario could be a solution?

5. For security reasons when an account is (re)enabled the password is also changed, that is, a new random password is generated.
This is also related to issue 4 because is a change of a user account which is synchronized with directory but not updated inside the security cache.

6. I added more descriptions on javadocs especially on the description of classes.

7. I fixed all issues related to use TAB characters instead spaces and space size identation.

#142 Updated by Greg Shah over 10 years ago

Code Review 1218a

Really good improvements. Yes, we are close now.

5. For security reasons when an account is (re)enabled the password is also changed, that is, a new random password is generated.
This is also related to issue 4 because is a change of a user account which is synchronized with directory but not updated inside the security cache.

I understand. All changes to the account are subject to this limit. It is by design.

I think that doing a short work around the SecurityManager and SecurityCache it is possible to add a method to update (refresh) a user account inside the SecurityCache instead to refresh the entire context.
We have to search on the list of accounts stored inside the SecurityCache instance and update only one account.
Do you think that this scenario could be a solution?

Yes, I do think this can work. This is what I was suggesting with my "option 1".

BUT I do think we must update all instances of that account. I believe there may be separate instances of those accounts in every cache generation since the account was created. This would mean that you must edit a list of instances of that account, otherwise when the latest generation is popped, the previous generation may have a different state and cause problems. If I am mis-remembering how we do this, let me know.

#143 Updated by Marius Gligor over 10 years ago

Unfortunately I found that changing the TAB characters in makefile results in a make error related to missing separator.
So I restored the makefiles to have TAB characters.

#144 Updated by Marius Gligor over 10 years ago

I did some small changes inside the security package to allow update of user accounts stored in security cache.

1. I added the method SecurityManager#updateCachedUserAccount which is restricted to be executed only from UpdateAccountTask.run

On running the AssociateThread force a SecurityContext by duplicating the current SecurityContextStack.
I tried to access the SecurityCache from the thread SecurityContext but no accounts were found in cache.
Finally after a lot of tests I found that is enough to search for accounts inside the SecurityCache of the current SecurityManager instance.
I added also the same method to SecurityCache#updateCachedUserAccount which iterate over the cached accounts collection looking for a specific account.
If the specified account is found only the enabled and password attributes are changed in cache.
The SecurityCache class has package-private modifier and could be accessed only from classes inside the package like SecurityManager.

2. Theoretical it's possible to generate the temporary accounts only in memory (pool) and inject the generated list of accounts directly into the SecurityCache list of accounts.
Thought that other threads like AdminConsole could do at one moment a SecurityCache refresh by calling the AdminServerImpl.targetRefresh this could cause problems.
The injected accounts will disappear from SecurityCache and the new SecurityCache instance will hold only accounts which are get from directory.
The same problems could occur also in case we do the account changes only in memory (security cache).
Supposing we change the password and enable attributed for an account into security cache and send the subject id and password to a spawned process.
If a new SecurityCache is requested by another thread before the spawned process is using the password to authenticate, than the enabled and password
for this account will be loaded from directory having other values and later the client (spawned process) authentication will fail.

With the current implementation this side effects does not occur because first the directory is updated 
follow by an cache updated. In case another thread will create a new SecurityCache the directory is in an
consistent state with in memory pool and the temporary accounts are loaded in cache from directory.

#145 Updated by Greg Shah over 10 years ago

Code Review 1219a

Everything looks good. Please get this regression tested (only runtime testing is needed).

I agree that my idea of having truly non-persistent accounts would require much more significant change to the security package. You did the right thing in choosing to implement in the simplest manner.

#146 Updated by Marius Gligor over 10 years ago

I've started to do the regression tests. When I executed this step:

run_regression.sh build && run_regression.sh save-generated -Dsave.candidate=y

the build of project fails due to the missing of PAM libraries when spawn.c is build.
I need this libraries to be installed on devsvr01 but I need root permissions for sudo apt-get install
Another option is to change the makefile.spawn and compile without PAM options.

#147 Updated by Marius Gligor over 10 years ago

In order to install PAM libraries we need to install this package:

sudo apt-get install libpam0g-dev

#148 Updated by Marius Gligor over 10 years ago

Another option is to define a build variable like -Dpam=yes in order to build spawn.c with PAM option.
By default the build will be without PAM options.
This is useful because other members of team should not install the libpam0g-dev on their workstations.

#149 Updated by Greg Shah over 10 years ago

Another option is to define a build variable like -Dpam=yes in order to build spawn.c with PAM option.
By default the build will be without PAM options.

This is a good idea. Please do make this change.

I have also just installed the PAM libraries on devsrv01, so that we can test both with and without PAM support.

#150 Updated by Marius Gligor over 10 years ago

OK. Now the project was successfully build on devsvr01.
Nevertheless I'm going to add the PAM variable for conditional build similar with skip "postbuild" target already fixed.

#151 Updated by Marius Gligor over 10 years ago

I added a conditional build variable ${have.pam} for Linux / Unix native build targets in build.xml script file.
The variable should be provided on the command line in order to build the spawn.c with PAM options.
By default the spawn.c is build without PAM options.
Example:
ant -Dhave.pam=yes native (build with PAM options)
or
ant -Dhave.pam=yes -Dpost.build=yes native (build with PAM options and do the postbuild target)

#152 Updated by Greg Shah over 10 years ago

Code Review 1219b

The changes look good.

#153 Updated by Greg Shah over 10 years ago

-------- Original Message --------
Subject:     regression tests
Date:     Fri, 20 Dec 2013 12:38:35 +0200
From:     Marius Gligor <mag@goldencode.com>
Reply-To:     mag@goldencode.com
To:     Constantin Asofiei <ca@goldencode.com>, Greg Shah <ges@goldencode.com>

Constantin,

The package that I started to test require some changes inside directory.
Do you think that missing this values from directory could cause a failure?

Thanks
Marius

1. The standard subject should be added  to admin ACL in order to create temporary account on start-up:

 <node class="container" name="system">
          <node class="container" name="000100">
            <node class="resource" name="resource-instance">
              <node-attribute name="reference" value="admin"/>
              <node-attribute name="reftype" value="TRUE"/>
            </node>
            <node class="systemRights" name="rights">
              <node-attribute name="check" value="true"/>
            </node>
            <node class="strings" name="subjects">
              <node-attribute name="values" value="admins"/>
              <node-attribute name="values" value="standard"/>
            </node>
          </node>

2. The node chuiWeb should be also added to directory used on client spawn.

   <node class="container" name="server">
      <node class="container" name="default">
        <node class="boolean" name="adminEnabled">
          <node-attribute name="value" value="TRUE"/>
        </node>
        <node class="container" name="chuiWeb">
          <node class="boolean" name="enabled">
            <node-attribute name="value" value="TRUE"/>
          </node>
          <node class="boolean" name="secure">
            <node-attribute name="value" value="TRUE"/>
          </node>
          <node class="string" name="spawner">
            <node-attribute name="value" value="/usr/share/p2j/spawn"/>
          </node>
          <node class="string" name="jvmArgs">
            <node-attribute name="value" value="-Xmx512m -XX:MaxPermSize=64m -Djava.awt.headless=true"/>
          </node>
          <node class="string" name="classpath">
            <node-attribute name="value" value="/usr/share/p2j/lib/p2j.jar"/>
          </node>
          <node class="string" name="libPath">
            <node-attribute name="value" value="/usr/share/p2j/lib"/>
          </node>
          <node class="string" name="workingDir">
            <node-attribute name="value" value="./"/>
          </node>
          <node class="string" name="configFile">
            <node-attribute name="value" value="config.xml"/>
          </node>
          <node class="integer" name="rows">
            <node-attribute name="value" value="24"/>
          </node>
          <node class="integer" name="columns">
            <node-attribute name="value" value="80"/>
          </node>
          <node class="string" name="background">
            <node-attribute name="value" value="0x000000"/>
          </node>
          <node class="string" name="foreground">
            <node-attribute name="value" value="0xFFA500"/>
          </node>
          <node class="string" name="selection">
            <node-attribute name="value" value="0x0000FF"/>
          </node>
          <node class="string" name="fontname">
            <node-attribute name="value" value="monospaced"/>
          </node>
          <node class="integer" name="fontsize">
            <node-attribute name="value" value="12"/>
          </node>
        </node>

In regard to this, please setup our code so that most of this has sensible default values when the backing node is missing from the directory. Perhaps everything except for chuiWeb/enabled can be defaulted.

This will greatly reduce the amount of configuration needed. The only tricky part is where there are platform-specific values (e.g. /usr/...), in such cases we will need different defaults by platform.

#154 Updated by Marius Gligor over 10 years ago

I provided default values for all this parameters (see ClientBuilderOptions).

Now the server starts without errors if directory settings are not in place:
- If node chuiWeb is missing the http://server_address:admin_port/chui returns HTTP 404 page not fond because no handler is registered for /chui target.
- If the server standard subject is not added to admin ACL the pool is empty and no client could be spawned.

The changes are currently in regression tests. At the end of tests if no errors I have to download the Majic build to my workstation add appropriate directory configuration
and do a test to spawn web clients.

#155 Updated by Greg Shah over 10 years ago

If the server standard subject is not added to admin ACL the pool is empty and no client could be spawned.

I'm fine with this, but I would like you to log a diagnostic message that reports that the web chui is enabled but the sufficient rights have not been granted to the standard subject such that the web interface can function properly.

Please upload the new code here. You can keep going with testing.

#156 Updated by Marius Gligor over 10 years ago

Also I could provide platform specific (Linux/Windows) default values and do only runtime tests with Majic build on my workstation without to add chuiWeb node to Majic directory.
The server standard subject MUST be added on tests.

#157 Updated by Greg Shah over 10 years ago

I still prefer you to test on devsrv01 with the chui web active. This will make sure that we have a real chui environment (MAJIC) to test the web chui driver when that is ready. TIMCO will also be very interested in using the chui web driver, so we need to make sure it works well for them.

Please work with Constantin to figure out how to make the changes to the directory template files that are being used for the devsrv01 environment.

Finally, it will be important to make sure that the code can optionally use relative pathing for all the jars, classpath, libpath etc... which will allow the same configuration to work for multple users in our testing environment. So instead of absolute paths /usr/... it would be relative to the user's home dir.

#158 Updated by Marius Gligor over 10 years ago

Classpath, native libraries path and spawner location should be shared by all web clients.
We cannot provide relative paths for this values we have to specify absolute paths.
With relative paths we have to replicate the jar's on all users home directory.

#159 Updated by Constantin Asofiei over 10 years ago

I think the solution for devsrv01 is not about setting relative paths, but computing the full paths dynamically for each user. Inside the directory template, we can set the i.e. /usr/share/p2j/lib/p2j.jar path to something like P2J_JAR and change the majic/run/server/prepare_dir.sh script so that these values are replaced with the proper full path for the current user. Keep in mind that each user on devsrv01 has a ~/testing/majic/p2j folder.

#160 Updated by Greg Shah over 10 years ago

We cannot provide relative paths for this values we have to specify absolute paths.

Most production environments would follow this approach, that is true.

With relative paths we have to replicate the jar's on all users home directory.

This is exactly what we already do in our testing environments. So this is OK and in fact it is desired, because each user may have different changes to test.

#161 Updated by Greg Shah over 10 years ago

Keep in mind that each user on devsrv01 has a ~/testing/majic/p2j folder.

This is why I prefer to use relative paths on devsrv01. It reduces the changes to be made in the directory template and it should still work.

#162 Updated by Constantin Asofiei over 10 years ago

Actually, I think we should be able to determine the library path and the classpath dynamically. See the code in AppServerLauncher.builder - there I determine the jar which stores the ClientDriver class and this will be added to the classpath; also, the assumption is that the library path is the parent folder of this jar.

What remains is the spawner folder - can you remind me what this is used for?

#163 Updated by Greg Shah over 10 years ago

I think we should be able to determine the library path and the classpath dynamically.

This will work as a default. But I do want to make sure that we can specify a completely independent installation. The idea: the server and clients may not even be on the same machine and/or the clients may not be allowed access to the directory structure in which the server runs.

#164 Updated by Marius Gligor over 10 years ago

We can detect the location of p2j.jar as you already mention and I use this method in the previous snapshots but I think this should be used
as a default value in case no other value is specified inside directory.
In other words we have to find a set of default values which are relative to user and override this values in directory if we need absolute or other values.
Please let me know if this is what you are looking for and if we can use as default values the following:

- p2j.jar location for classpath native libraries directory and spawner location.
- user (current) directory for default working directory.

They are also many other issues that should be solved related to spawner build.
In Linux the postbuild target is mandatory and require sudo permissions.
Are all users (developers) on devsvr01 permissions tu use sudo if they want to do tests?

#165 Updated by Marius Gligor over 10 years ago

Also keep in mind that the p2j.jar location detection is done on the server instance
and is absolute to the user on which the server is running.
For other users this method of location detection does not work.

#166 Updated by Greg Shah over 10 years ago

Here are some details on our approach in regard to javascript frameworks.

1. Cross-browser compatibility is still a problem. Primarily I refer here to issues caused by browsers implementing the same features in incompatible ways (e.g. the IE event model versus everyone else).
2. Javascript standards move slowly and many problems that have yet to be standardized are already solved cleanly by existing frameworks.
3. Uneven implementation of existing javascript standards across browsers.
4. The event-driven (asynchronous) + single-threaded nature of javascript can lead to a great deal of boilerplate code being needed for things that in other languages would be shorter and simpler. Often javascript frameworks provide mechanisms to reduce this.
5. There are common javascript problems that can be solved with helper functions and other utility features which enhance productivity and simplify code.

I think it is reasonable to use a javascript framework to reduce or eliminate the above issues.

I have taken a close look at jQuery and Dojo. I've taken a more cursory look at Prototype, GWT and a few others. Based on what I have seen, Dojo seems to be the best option for "web applications". It can also be used for simpler web pages, wherever javascript is used. But it seems to really shine when one is developing an entire web app in javascript. In other words, when the user is not moving from page to page, but instead there is some technique (e.g. AJAX, websockets...) that is used to communicate to the back end server while the javascript drives a dynamic interface on a single page load.

The open source licensing, breadth of features and depth of features of Dojo are all very good. It is also moving forward at a fast pace. As a technology it is vibrant and healthy.

The plan is to include the necessary Dojo files from our own local installation instead of from a content network. Where we can do something simply in javascript that isn't enhanced or made more simple by using Dojo, then we should use our own javascript. But we don't need to be worried about trying Dojo functionality if it makes sense.

#167 Updated by Greg Shah over 10 years ago

Also keep in mind that the p2j.jar location detection is done on the server instance and is absolute to the user on which the server is running.

This will be fine for our use on devsrv01. It may also be good for other simple test environments. Production environments can configure more explicit choices, so that is OK.

Are all users (developers) on devsvr01 permissions tu use sudo if they want to do tests?

This hasn't been done yet. Unfortunately, I have to step out for a couple of hours.

Constantin: can you make the sudoers file changes? See the postbuild.sh for details.

#168 Updated by Constantin Asofiei over 10 years ago

Greg Shah wrote:

Constantin: can you make the sudoers file changes? See the postbuild.sh for details.

Yes, I can make the change. Just confirm that this line needs to be added (i.e. for user mag):

mag ALL=(ALL) NOPASSWD: /home/mag/testing/majic/p2j/src/native/postbuild.sh

#169 Updated by Marius Gligor over 10 years ago

Here are the latest changes that I made. Monday I want to do runtime tests first on my workstation and then on devsvr01.
I have to download the Majic build on my station add the later p2j build from my workspace and change the server directory.
If the running tests works on my station than I have to upload the p2j build and changed directory on devsrv01 and do runtime tests for user mag.
Constatin please let me know if is this OK. Are you available on Monday to help me if necessary?

#170 Updated by Constantin Asofiei over 10 years ago

Marius Gligor wrote:

Monday I want to do runtime tests first on my workstation and then on devsvr01.

Regression testing can be ran only on devsrv01; what is possible on your machine is to run a local MAJIC server. For this, you need to download the MAJIC jars and other setup (without converted and generated sources) to your machine. If MAJIC is starting OK and clients can login, then you can move to regression testing on devsrv01.

Constatin please let me know if is this OK. Are you available on Monday to help me if necessary?

Yes, I'll be available on Monday.

BTW, you need sudo rights too, beside the line at note 168, right? I'll make the sudo-related changes on Monday (if Greg doesn't get a chance to make them).

#171 Updated by Marius Gligor over 10 years ago

I did something similar with AdminConsole if you remember.
After the local tests you suggest me to repeat the regression tests?
What I want to do is to start the Majic server on devsrv01 and try to access https://localhost:7443/chui from my web browser.
But I'm afraid will not work 100% due to the redirection on a random port which require a ssh tunnel.

#172 Updated by Constantin Asofiei over 10 years ago

Marius Gligor wrote:

After the local tests you suggest me to repeat the regression tests?

Yes

What I want to do is to start the Majic server on devsrv01 and try to access https://localhost:7443/chui from my web browser.
But I'm afraid will not work 100% due to the redirection on a random port which require a ssh tunnel.

For this reason is best to start the Majic server on your machine (that's why we allow remote connections to each user's dedicated postgres cluster on devsrv01). Have you done this before?

#173 Updated by Marius Gligor over 10 years ago

Yes I did that when I tested Admin Console on my local station. So I have to do tests on my local station and after that redo regression tests with the latest changes.

#174 Updated by Marius Gligor over 10 years ago

Related to JS frameworks. I have some experience using jQuery but no experience using other frameworks.

#175 Updated by Marius Gligor over 10 years ago

On Saturday and today I did a lot of tests on a local copy of Majic project.
Finally I succeeded to configure the directory nodes for Majic with properly values.
Using these settings the web client spawn process works properly.
The steps and the changes that I did are described on the attached file.
I attached also the latest changes that I did (minor changes) as a result of my tests and the merged changes on StandardServer.java committed today by Ovidiu.

#176 Updated by Constantin Asofiei over 10 years ago

Marius: I've made the sudo-related changes for your account on devsrv01. After regression testing passes, make sure the /etc/sudoers.d/sudoers-ext contains the settings for all other users.

#177 Updated by Greg Shah over 10 years ago

Go ahead with regression testing on devsrv01 without making changes to enable the web chui. That will allow us to get this code checked in sooner.

Meanwhile, Constantin and I will make the devsrv01 changes needed to enable the web chui in the near future. In regard to the sudo approach, I have been thinking about it. The current plan needs to be changed because it opens a security hole.

The core issue is the path to the postbuild.sh and to the target spawn executible are both under user control. That means that the script can be edited to do anything at all. Likewise, the spawn source code is under the control of the user leading to the same problem. Although we could go with an approach that puts the specific commands (chown, chmod including their specific arguments so that the commands are very limited in scope) into sudoers intead of the script, that only protects from the modification of postbuild.sh.

Just as our customers would do, I think we will manually install a prebuilt spawn to a common location. Then we will not need sudo rights for each user. The downside is that changes can't be automatically applied.

#178 Updated by Marius Gligor over 10 years ago

Constantin: I think that is no need to do that. This issue should be tested only on a local copy of Majic not on devsvr01.
The currently regressions tests scripts does not test the spawn of web client.
Greg: Should I repeat the regressions tests on devsvr01 using the current changes?

#179 Updated by Constantin Asofiei over 10 years ago

Marius Gligor wrote:

Constantin: I think that is no need to do that. This issue should be tested only on a local copy of Majic not on devsvr01.

OK, I've backed out the sudo changes. But, the problem is: you need to change the "native" task so that sudo rights are not required; with your current build, you need sudo rights on each machine were P2J needs to be built (and the native task is executed by default - maybe make a separate task to build the spawner?).

#180 Updated by Marius Gligor over 10 years ago

Actually the build does not require sudo permissions.
You have to use some command line arguments to execute the postbuild.sh

-Dhave.pam=yes // used to compile with PAM options
-Dpost.build=yes // to execute the postbuild target which in turn run postbuild.sh script

The normal ant task
ant native
will build the native artifacts without ask for sudo permissions.

The following build (manual)
ant -Dhave.pam=yes -Dpost.build=yes native
will ask for sudo password and you shoul have PAM libraries installed on machine.

#181 Updated by Constantin Asofiei over 10 years ago

Marius Gligor wrote:

Actually the build does not require sudo permissions. You have to use some command line arguments to execute the postbuild.sh

Ahh, I missed that, is OK then.

#182 Updated by Greg Shah over 10 years ago

Code Review 1223a

I am fine with the changes.

Please merge up to the latest bzr revision (10426) and then run runtime regression testing on devsrv01. Do not test the web chui on devsrv01, just get it past regression testing so that we can get it checked in.

#183 Updated by Greg Shah over 10 years ago

We have to design the protocol used by websockets.
We have two options: JSON or binary payloads. As I remember you said that you prefer a binary protocol.

Yes, for network efficiency it is important this is binary.

Are you already designed such a protocol?

No.

#184 Updated by Greg Shah over 10 years ago

Additional Details on the Protocol

1. The protocol will need to be full-duplex. It must be possible to have both sides sending/recving independently.

One big reason for this is that even when the normal client processing is blocked (because the flow of control for the app is executing on the "server" side), the end-user can still execute a CTRL-C which should cause an interruption of the server. It is important that you review the CTRL-C processing which we implement today. It is a bit complex because we have split a single threaded process into 2 processes that are connected via a socket for interprocess communications (IPC). We use the "conversation" thread on both sides of the socket to force this to behave like a single-threaded environment even though we are inherently multi-threaded. In other words, we make all the primary control flow paths operate synchronously. But since the CTRL-C is an asynchronous event, it must interrupt the flow of control wherever it is executing (locally in the P2J client or remotely on the P2J server). This is where it is tricky. Please review the following files to understand this better:

com/goldencode/p2j/net/Control.java
com/goldencode/p2j/net/Conversation.java
com/goldencode/p2j/net/Dispatcher.java (search on RoutingKey.INTERRUPT_SESSION)
com/goldencode/p2j/net/InterruptHandler.java
com/goldencode/p2j/util/InterruptedExceptionHandler
com/goldencode/p2j/ui/chui/ThinClient.java (see watchCtrlC())
com/goldencode/p2j/ui/client/TypeAhead.java (search on Key.VK_CTRL_C)
com/goldencode/p2j/ui/client/Watcher.java

I hope that you can simply deliver the CTRL-C event to the P2J client side and let the rest of the infrastructure execute as is coded.

2. It is unknown if we need a full remote procedure call (RPC) implementation. I would like to avoid it if possible, BUT I don't want to make a poor solution just to avoid such an implementation. Our standard P2J protocol (see the net package for the Protocol, Queue, Dispatcher and Message classes) is message based and we implement both synchronous (RPC) and asynchronous (events) using the same transport.

3. As mentioned above, the message encoding must be very efficient and we already know we want to go binary. There are a couple of existing open source options that can mix JSON with a binary transport (UBJSON and messagepack).

4. On the javascript side, we may want (or even need) to use Shared Workers (background threads) and MessagePorts/MessageChannel to implement the CTRL-C notification. Or maybe that is not necessary.

Start by focusing on the requirements for the protocol (based on what services the java driver will need and what services the javascript side will need). Document your findings here. You should also investigate the binary JSON options. If those are not useful/appropriate, we an write our own.

#185 Updated by Greg Shah over 10 years ago

Something else to consider with the protocol is that the GUI driver/javascript will have more significant requirements (and a different driver interface) than can be seen right now with the ChUI driver.

Mouse events (movement, clicks...), fonts, images, drag and drop and a whole different level of drawing will be needed at a minimum.

#186 Updated by Marius Gligor over 10 years ago

Here are the latest changes on my local repository after an update from remote repository.
Only two files was merged on update StandardServer.java and ClientCore.java

#187 Updated by Greg Shah over 10 years ago

Code Review 0107a

I am fine with the changes. When it passes regression testing, please check it in and distribute it.

#188 Updated by Marius Gligor over 10 years ago

Ref. #2201 implement the web client process spawning and server redirect.
Passed the regression tests.
Committed revision 10429.

added src/com/goldencode/p2j/main/ClientBuilder.java
added src/com/goldencode/p2j/main/ClientBuilderOptions.java
added src/com/goldencode/p2j/main/ClientBuilderParameters.java
modified src/com/goldencode/p2j/main/ClientCore.java
added src/com/goldencode/p2j/main/ServerKeyStore.java
added src/com/goldencode/p2j/main/Spawner.java
added src/com/goldencode/p2j/main/SpawnerImpl.java
added src/com/goldencode/p2j/main/SpawnerListener.java
modified src/com/goldencode/p2j/main/StandardServer.java
added src/com/goldencode/p2j/main/TemporaryAccount.java
added src/com/goldencode/p2j/main/TemporaryAccountPool.java
added src/com/goldencode/p2j/main/TemporaryAccountWorker.java
added src/com/goldencode/p2j/main/UpdateAccountTask.java
added src/com/goldencode/p2j/main/WebClientSpawner.java
modified src/com/goldencode/p2j/main/WebHandler.java
modified src/com/goldencode/p2j/security/SecurityCache.java
modified src/com/goldencode/p2j/security/SecurityManager.java
modified src/com/goldencode/p2j/ui/client/chui/driver/swing/ChuiSimulator.java
modified src/com/goldencode/p2j/ui/client/chui/driver/web/ChuiWebDriver.java
added src/com/goldencode/p2j/ui/client/chui/driver/web/WebScreenDriver.java
added src/com/goldencode/p2j/ui/client/chui/driver/web/chui_web.html
modified src/com/goldencode/p2j/ui/client/swing/SwingHelper.java
modified src/com/goldencode/p2j/web/GenericWebServer.java
modified src/native/makefile
added src/native/makefile.spawn
added src/native/postbuild.sh
added src/native/spawn.c
added src/native/winspawn.c
modified build.xml

#189 Updated by Marius Gligor over 10 years ago

1. WebSockets are full-duplex communication channels over TCP. So we have to implements a communication protocol on server side in Java and on the client side using JS.
Maybe a short research on how to use GZIP with embedded jetty could be useful.
Do you think that a JSON and GZIP could be an alternative to binary compressed JSON?

2. At this stage I have a general overview over the implementation issues.
On the client side the protocol should looks like:

- The input events from keyboard and mouse are send over the network using the WebSockets and binary protocols toward chui web driver.
- The commands from chui driver on server side are send over the network toward the client page and rendered on the screen.
- The java script code on client side should manage a video memory array to render the output commands on a canvas, a screen cursor, the keyboard
and the mouse events as input commands send to server.

On the server side:
- The output commands from chui screen driver are send to client where are rendered on the screen or on the speaker (beep command)
- The inputs coming from client consisting in keystrokes and mouse events should be enqueued on the server side and dequeued on screen driver requests.
Regarding the keystrokes and CTRL-C handle in particular, according to my researches could be easily implemented to use the existing flaw with no changes in code.
The ScreenDriver define a readKey() method used to get keystrokes from a type-ahead buffer using a ChuiSimulator instance.
Our ChuiWebDriver use an instance of ChuiWebSimulator. ChuiWebSimulator extends ChuiSimulator and override the readKey() method.
So far we have a simple implementation for readKey() method which should be improved if necessary.

3. Dojo toolkit is distributed as a zip file containing a lot of Dojo modules (short java script files)
The latest release is 1.9.2 which is around 13 Mb in size. (the compressed format) A new release 2.0 is expected earlier this year.
The best way to handle the Dojo modules inside p2j project is to put the zip file on the classpath (add to the list of dependencies inside the manifest file)
and access modules as classpath resources.
In order to do that I have to design a separate jetty Handler class which handle all Dojo targets and add this handler to the embedded server handlers collection.
I think that is better to use the Dojo toolkit wherever is possible in our java script code for example in WebSockets communication implementation, DOM manipulation etc.

4. I think also that we have to use canvas HTML5 API which in turn restricts the use of application only on web browsers HTML5 compliant.

#190 Updated by Greg Shah over 10 years ago

Maybe a short research on how to use GZIP with embedded jetty could be useful.
Do you think that a JSON and GZIP could be an alternative to binary compressed JSON?

I am willing to consider this. However, I am generally concerned with any approach that encodes more data than necessary and uses compression to "fix" the issue. Ultimately, the network traffic is reduced but by trading CPU cycles.

We should also consider just implementing our own non-JSON binary message passing protocol where we handle the encoding/decoding ourselves.

2. At this stage I have a general overview over the implementation issues.

Yes, this all sounds like you are going in the right direction.

Dojo toolkit is distributed as a zip file containing a lot of Dojo modules (short java script files)
The latest release is 1.9.2 which is around 13 Mb in size. (the compressed format) A new release 2.0 is expected earlier this year.

We should not need the full distribution, especially for the ChUI support.

Dojo has 2 things that may help us:

http://dojotoolkit.org/documentation/tutorials/1.9/build/ - a build toolset to create a custom dojo distribution (that can be much smaller)
http://dojotoolkit.org/documentation/tutorials/1.9/modules/ - asynchronous module definition support to make code modular in a standard way

The best way to handle the Dojo modules inside p2j project is to put the zip file on the classpath (add to the list of dependencies inside the manifest file) and access modules as classpath resources.

I can see this may be needed. But I wonder if we can't customize/minimize our distribution using the Dojo build tools and get a simpler result. Please review and respond.

I think that is better to use the Dojo toolkit wherever is possible in our java script code for example in WebSockets communication implementation, DOM manipulation etc.

If we can write simple non-Dojo javascript to do a job, I prefer that option except for the following cases:

1. The javascript must be browser-specific and/or would have compatibility issues and Dojo has a clean solution.
2. The Dojo approach provides a much cleaner result because it has functionality that would have to be otherwise duplicated ourselves.

This means that the Dojo code may indeed be used often. But I don't want to default to writing Dojo code. Here is why:

1. Dojo changes rapidly and the more dependent we are upon it, the more effort there is to keep up.
2. High levels of dependency on Dojo will make it harder or more costly to get rid of it/replace it later, if ever the time comes where that is desired or necessary.
3. More code and complexity will be added to the project than may be strictly needed. With more code and complexity comes more bugs and a harder to maintain/understand code base.

I want to find the right balance. So the first time you plan to use Dojo for some particular feature, please consider the above and document (here) why it makes sense to use it, based on the criteria above.

4. I think also that we have to use canvas HTML5 API which in turn restricts the use of application only on web browsers HTML5 compliant.

Indeed, this is certainly true. It is not a problem.

#191 Updated by Marius Gligor over 10 years ago

1. I think that is better to start by designing our custom binary protocol. This means that we have to define the structure
of binary packages for each communication between web server and browser JS code.

2. The Dojo toolkit SDK allow us to do the following:

- To build a custom toolkit minimized and optimized containing only the used dependencies.
The result is a zip file containing only an subset of Dojo modules. The size of the zip file and the modules
could be reduced which is useful for us. I did a custom build today and it works fine.
However because we are using an embedded web server all the resources (web pages, css files, js files, image files, etc.)
should be delivered to browsers using jetty Handlers which are designed as Java classes.
So far I started to design such a Handler for Dojo toolkit using a full build Dojo toolkit minimized (13 Mb) as a zip file resource.
The requested JS modules are get from the zip file on request and serialized as HTTP response back to requester.
Finally I have to substitute this Dojo toolkit with a custom build containing only the used dependencies.
- To integrate the user applications with Dojo toolkit. Because we decided to avoid Dojo dependencies as much as possible 
this option is not in our interest.

3. For CHUI application we have to manage the following resources using WebSockets and JS code:

- Screen and cursor rendering. This could be done using HTML5 Canvas API without using any Dojo toolkit modules.
- Keyboard and mouse events. Here the Dojo toolkit could help us because this part depend on browser implementation so my suggestion is to use it.
- WebSocket communication. Here the Dojo toolkit added JS code (Long-Polling) to emulate WebSockets communications on web browsers (or servers)
which doesn't support the standard WebSockets API. Because our jetty server support standard WebSockets API and
the API is simple I think that we could design our custom JS standard module. (no other JS frameworks like Dojo).
Also I think that we will use only WebSockets API no other protocols supported by Dojo toolkit.

3. For a GUI application Dojo toolkit modules could help us but for the moment I did no investigations on this area.

#192 Updated by Greg Shah over 10 years ago

1. I think that is better to start by designing our custom binary protocol.

OK, good.

2. The Dojo toolkit SDK allow us to do the following:

OK.

Screen and cursor rendering. This could be done using HTML5 Canvas API without using any Dojo toolkit modules.

Yes.

Keyboard and mouse events. Here the Dojo toolkit could help us because this part depend on browser implementation so my suggestion is to use it.

Agreed, use Dojo here.

Because our jetty server support standard WebSockets API and the API is simple I think that we could design our custom JS standard module. (no other JS frameworks like Dojo). Also I think that we will use only WebSockets API no other protocols supported by Dojo toolkit.

Just like with HTML5 canvas, we are making it a requirement that only browsers that support websockets will be supported in our ajax driver. So it is OK to code our own approach directly using the standard websockets API instead of Dojo.

3. For a GUI application Dojo toolkit modules could help us but for the moment I did no investigations on this area.

This is a question we will answer later.

#193 Updated by Marius Gligor over 10 years ago

I designed minimal JS modules for Screen, Screen cursor, Message Beep and WebSocket communication management.
The attached archive does not contains the Dojo toolkit distribution (dojo-release-1.9.2.zip).
On the next step I'm going to design and implements the protocol to manage "Screen" and "Screen cursor" vis WebSockets.

#194 Updated by Greg Shah over 10 years ago

Code Review 0110a

I am fine with the general approach and how the code is progressing.

I only have 3 comments:

1. Although we don't have coding standards for Javascript, please consider that all Java coding standards are basically the same standards we should use for Javascript.

2. Please think about breaking the JS code into smaller modules. I'm not sure if we are at that point of needing it yet with tty.js, but I can see that we probably will need it before the chui JS client is done.

3. The HTML formatting should follow most of the same Java standards concepts. I know this may sound weird. But I think many of the standards make sense in general. For example, your HTML usually has no indents, but I would expect us to use indents of 3 spaces for each nested level like:

<!DOCTYPE html>
<html>
   <head>
      <script language="javascript" type="text/javascript" src="tty.js">
      </script>
      <script>
         window.onload=function()
         {
            tty.initSound('beep');
            tty.initScreen('screen', 25, 80, 'monospace', 20);
            tty.initWebSocket('wss://' + window.location.host + '${context}/ajax');
         };
      </script>
   </head>
   <body>
   </body>
</html>

#195 Updated by Marius Gligor over 10 years ago

I did a lot of improvements inside the JS code:
- I'm using JS sub modules stored on separate script files.
- Screen cursor improvements (could be sold/line, blinking/unblinking, visible/not visible)
- Use place holders in html page to provide initial values.

#196 Updated by Marius Gligor over 10 years ago

The following classes are used to emulate a terminal:

ChuiWebDriver extends (ChuiScreenDriver implements ScreenDriver)
ChuiPrimitives extends DriverPrimitives
ChuiWebSimulator extends (ChuiSimulator extends JComponent implements Printable)

The following methods are used by the terminal driver:

ChuiScreenDriver: ================
O beep() -> sim.beep()
O clear() -> sim.clear();
I readKey() -> sim.readKey();
O setCursorStatus(boolean on) -> sim.setCursorStatus(on);

ChuiPrimitives: ===============
O append(String str, Color color) -> sim.append(data);
O cursorAt(int x, int y) -> sim.setCursor(x, y);
O cursorStay(int x, int y) -> sim.setCursor(x, y);
I getX() -> sim.getCursorX();
I getY() -> sim.getCursorY();
I screenHeight() -> sim.getRows();
I screenWidth() -> sim.getColumns();
O sync() -> sim.triggerRepaint(true);
O updateScreen(ScreenData screen) -> sim.replace(screen.asArray(), screen.getCursorX(), screen.getCursorY());

Translation of driver calls into simulator calls:

ChuiScreenDriver,ChuiPrimitives ChuiSimulator I/O Remarks
(ChuiWebDriver) (ChuiWebSimulator) ===============================================================================================
beep() beep() O
clear() clear() O
readKey() readKey() I
setCursorStatus(on) setCursorStatus(on) O
append(str, color) append(data) O
cursorAt(x, y) setCursor(x, y) 0
cursorStay(x, y) setCursor(x, y) 0
getX() getCursorX() I (update by mouse event)
getY() getCursorY() I (update by mouse event)
screenHeight getRows() I (from configuration)
screenWidth getColumns() I (from configuration)
sync() triggerRepaint(true) O
updateScreen(screen) replace(arry, x, y) O

A possible binary protocol might looks like:

(O)utput Protocol Packet ===============================================================================================
beep() 0x80
clear() 0x81
setCursorStatus(on) 0x82 // cursor on
0x83 // cursor off
setCursor(x, y) 0x83 x y // new x, y cursor position
setCursor(x, y) 0x83 x y // new x, y cursor position
triggerRepaint(full) 0x84 size cell cell cell ... cell // synchronize screen memory
// cell = [x, y, colour, character]
// should be efficient implemented
// the cell list should contains only the changed cells.

(I)nput Protocol Packet ===============================================================================================
KeyEvent 0x00 meta key // meta = CTRL, ALT, SHIFT etc.
// key = key code
MouseEvent 0x01 meta x y // meta = click, contextmenu, dblclick
// x, y = mouse cursor position (row and col not pixels)

#197 Updated by Marius Gligor over 10 years ago

For a better view.

#198 Updated by Greg Shah over 10 years ago

It looks good (both the code in 0113a and the proposed protocol).

#199 Updated by Marius Gligor over 10 years ago

Today I started to implements the WebSockets protocol for screen management (server -> browser JS)
Please look at the attached picture in order to see the results (Ask.p)

Some observations:

The Cell class in the current CHUI screen implementation has two members, a character value and a Color object.
According to my researches the Color is not used to render the character, in other words the current implementation is for a monochrome terminal.
Please let me know if my assertion is true or not.
Finally I decided to use a JSON format for payload because is more easily to manage in JS.
Binary payloads require more processing in JS which it's a very slow process.
Only the operation to synchronize the screen is subject to be optimized.
The rest of operations are very simple and the JSON payloads are very small.
So far due to the existing implementation in p2j all cells in the screen are send as a text message.
On the client side only the changed values are rendered in other words the rendering process is optimized because this is a slow process in JS.
Regarding the network performances for an Ethernet MTU of 1500 bytes in size only 2 packets are used to send the entire screen over the network.
According to my tests this implementation seems to be very fast on my workstation.
Nevertheless we can think to further improvements but only after we have also the input protocol in place.
Also if we need colour support for cells inside converted P4G applications we have to do some changes.
For more details please look at ChuiWebSimulator class.

#200 Updated by Greg Shah over 10 years ago

The code changes in 0114a look good. There is some missing javadoc, but otherwise things seem pretty much on target.

According to my researches the Color is not used to render the character, in other words the current implementation is for a monochrome terminal.
Please let me know if my assertion is true or not.

It is true for today. But Hynek is working on proper COLOR support in #2037 and so this is about to change (in the next 2 weeks).

Finally I decided to use a JSON format for payload because is more easily to manage in JS.
Binary payloads require more processing in JS which it's a very slow process.

I know that other implementations have found it possible to improve performance using binary transfers. Have you reviewed the contents of UBJSON (http://http://ubjson.org/)?

We can go text for now, but we may need to shift this approach to binary in the future.

#201 Updated by Marius Gligor over 10 years ago

I've started to implements the protocol for keyboard management.
I have good news! So far It's works fine see the attached picture.
However invoking the primes.p results in server crash because my converted application is obsolete.
Since the time of my conversion many other changes were done on the project.
So I tried to do a new conversion but unfortunately without success.
Is the testcases project updated?
Should I do an update for testcases project from the remote repository?

#202 Updated by Constantin Asofiei over 10 years ago

Marius Gligor wrote:

So I tried to do a new conversion but unfortunately without success.

What command do you use for conversion? When converting directly under the uast/ folder, is best to pass an explicit file list to the ConversionDriver.

Should I do an update for testcases project from the remote repository?

Yes, you should update it (there are changes in some scripts, like server.sh, not just tests).

#203 Updated by Marius Gligor over 10 years ago

According to conversion handbook:

export P2J=/mag/p2j/to/p2j
export CLASSPATH=$P2J/build/lib/p2j.jar

java com.goldencode.p2j.convert.ConversionDriver -d2 f2+m0+cb ask.p primes.p

#204 Updated by Constantin Asofiei over 10 years ago

And what error do you get?

#205 Updated by Marius Gligor over 10 years ago

Marius Gligor wrote:

According to conversion handbook:

export P2J=/home/mag/p2j
export CLASSPATH=$P2J/build/lib/p2j.jar

java com.goldencode.p2j.convert.ConversionDriver -d2 f2+m0+cb ask.p primes.p

#206 Updated by Marius Gligor over 10 years ago

I attached the build log.

#207 Updated by Constantin Asofiei over 10 years ago

Make sure you are using the latest revision (and P2J is built). From the log, you have an outdated rules/annotations/record_scoping_prep.rules file.

#208 Updated by Marius Gligor over 10 years ago

My p2j project is updated and the build all is OK, no errors. However the conversion does not work.
The rules/annotations/record_scoping_prep.rules file size is 24.326 bytes and the date is 6 December 2013 13:58

#209 Updated by Constantin Asofiei over 10 years ago

Then I think there is some other p2j/ installation on the path; and I can't be of much help with that. Why I say this: from the conversion log, the failure is at this expression:

buf.registerBuffer(uniqueName, bufName, schemaName, javaName, getNoteString("dbname"), (level == -1), upPath("TRIGGER_PROCEDURE/KW_OLD") or upPath("STATEMENT/KW_ON/KW_OLD/"))

but in the record_scoping_prep line 406 (file history H019 - this is what we use to check the file version) there is this expression:
               buf.registerBuffer(uniqueName,
                                  bufName,
                                  getNoteString("force_dmo_alias"),
                                  schemaName,
                                  javaName,
                                  getNoteString("dbname"),
                                  (level == -1),
                                  upPath("TRIGGER_PROCEDURE/KW_OLD") or
                                     upPath("STATEMENT/KW_ON/KW_OLD/"),
                                  isNote("is_dynamic_tables") and
                                     getNoteBoolean("is_dynamic_tables"))

See how the getNoteString("force_dmo_alias") and isNote("is_dynamic_tables") and getNoteBoolean("is_dynamic_tables") arguments are missing from the logged expression.

Search your drive for the record_scoping_prep.rules files and see which P2J has the incorrect file.

#210 Updated by Marius Gligor over 10 years ago

Thanks I fixed the problem.
I just copied the rules directory from p2j on testcases/uast/p2j/rules and the conversion was done.

#211 Updated by Greg Shah over 10 years ago

Please note that you should always have a symbolic link in your project home (testcases/uast/) to the p2j/ directory that you are using.

#212 Updated by Greg Shah over 10 years ago

I've started to implements the protocol for keyboard management.
I have good news! So far It's works fine see the attached picture.

That is AWESOME! I am very excited to see this come together. Great work!

#213 Updated by Marius Gligor over 10 years ago

I did a new conversion for Ask.p and Primes.p
Unfortunately when I want to start the primes.p on my emulator the server and the client crash.
The same results I obtained when I'm using the swing client (see the attached files)!
Seems to be a common problem but I'm not sure if is the result of my recently changes.
Could you help me please with some hints?

#214 Updated by Constantin Asofiei over 10 years ago

Take the testcases/simple/server/server.sh from the repository. The problem is caused by the fact that the server is missing this JVM argument:

-Djava.system.class.loader=com.goldencode.p2j.classloader.MultiClassLoader

#215 Updated by Marius Gligor over 10 years ago

Now seems to be a serialization problem.

WARNING: {Reader} failure in reading loop
java.io.InvalidClassException: com.goldencode.p2j.util.NumberType; local class incompatible: stream classdesc serialVersionUID = -505777196529990035, local class serialVersionUID = 4677557220506711006
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:617)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1620)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1515)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1620)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1515)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1620)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1515)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1769)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1348)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370)
at com.goldencode.p2j.ui.client.WidgetEntry.readExternal(WidgetEntry.java:116)
at java.io.ObjectInputStream.readExternalData(ObjectInputStream.java:1835)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1794)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1348)
at java.io.ObjectInputStream.readArray(ObjectInputStream.java:1704)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1342)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370)
at com.goldencode.p2j.ui.ScreenBuffer.readExternal(ScreenBuffer.java:929)
at java.io.ObjectInputStream.readExternalData(ObjectInputStream.java:1835)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1794)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1348)
at java.io.ObjectInputStream.readArray(ObjectInputStream.java:1704)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1342)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370)
at com.goldencode.p2j.net.Message.readExternal(Message.java:367)
at com.goldencode.p2j.net.SyncMessage.readExternal(SyncMessage.java:102)
at java.io.ObjectInputStream.readExternalData(ObjectInputStream.java:1835)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1794)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1348)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370)
at com.goldencode.p2j.net.Protocol$Reader.byteArrayToObj(Protocol.java:436)
at com.goldencode.p2j.net.Protocol$Reader.run(Protocol.java:347)
at java.lang.Thread.run(Thread.java:724)

com.goldencode.p2j.net.SilentUnwindException: Connection ended abnormally
at com.goldencode.p2j.net.Queue.transactImpl(Queue.java:1140)
at com.goldencode.p2j.net.Queue.transact(Queue.java:583)
at com.goldencode.p2j.net.BaseSession.transact(BaseSession.java:179)
at com.goldencode.p2j.net.HighLevelObject.transact(HighLevelObject.java:163)
at com.goldencode.p2j.net.RemoteObject$RemoteAccess.invokeCore(RemoteObject.java:1416)
at com.goldencode.p2j.net.InvocationStub.invoke(InvocationStub.java:97)
at com.sun.proxy.$Proxy4.standardEntry(Unknown Source)
at com.goldencode.p2j.main.ClientCore.start(ClientCore.java:260)
at com.goldencode.p2j.main.ClientCore.start(ClientCore.java:80)
at com.goldencode.p2j.main.ClientDriver.start(ClientDriver.java:223)
at com.goldencode.p2j.main.CommonDriver.process(CommonDriver.java:423)
at com.goldencode.p2j.main.ClientDriver.process(ClientDriver.java:1)
at com.goldencode.p2j.main.ClientDriver.main(ClientDriver.java:241)
Caused by: com.goldencode.p2j.net.ApplicationRequestedStop: Queue stop is requested by application
at com.goldencode.p2j.net.Conversation.block(Conversation.java:304)
at com.goldencode.p2j.net.Conversation.waitMessage(Conversation.java:257)
at com.goldencode.p2j.net.Queue.transactImpl(Queue.java:1126)
... 12 more

#216 Updated by Constantin Asofiei over 10 years ago

The problem is you have two distinct P2J builds - one for the server, and one for the client. And the serialVersionUID in the two builds (For a certain class) are not the same. You should always have a single P2J build and symlink to it as necessary, as the P2J server and the P2J client must use the same compiled sources.

#217 Updated by Marius Gligor over 10 years ago

OK Thanks. Now the SWING driver works fine.
I have configurations on my IDE to start the server and the client which helps on development.
I have to find a solution to change also my IDE run configurations.

#218 Updated by Constantin Asofiei over 10 years ago

The swing chui client can be started with no problem from Eclipse (and I guess from other IDE too); just set it up like this:
  • working dir: testcases/simple/client
  • main class: com.goldencode.p2j.main.ClientDriver
  • program args: client.xml; its content is (change the ports accordingly):
    <?xml version="1.0"?>
    <node type="client">
       <net>
          <server secure="false" />
          <server host="localhost" />
          <server secure_port="3334" />
          <server insecure_port="3434" />
       </net>
       <client>
          <driver type="swing_chui_frame"/>
          <chui rows="24"/>
          <chui columns="80"/>
          <chui background="0x000000"/>
          <chui foreground="0xFFA500"/>
          <chui selection="0x0000FF"/>
          <chui fontname="monospaced"/>
          <chui fontsize="12"/>
       </client>
    </node>
    
  • JVM args: -Dfile.encoding="UTF-8" -XX:+HeapDumpOnOutOfMemoryError -Xdebug -Xnoagent -Djava.compiler=NONE -Djava.library.path=../../../build/lib/ (library path can be changed as needed)

#219 Updated by Marius Gligor over 10 years ago

OK. Now it works from both command line and from IDE.
I attached some well known screens this time taken from my web browser :-)

#220 Updated by Greg Shah over 10 years ago

Very cool stuff. Please use testcases/uast/keys.p to test out your key processing. Compare the results to running the Swing client and the NCURSES client.

#221 Updated by Greg Shah over 10 years ago

By the way, our current ChUI client (swing and ncurses) does not support mouse events. You don't need to add support for mouse to the AJAX client at this time.

#222 Updated by Marius Gligor over 10 years ago

Today I started to implements the keyboard events protocol.
So far I tested:

-printable characters OK
-CTRL-C OK
-beep sound OK
-mouse events removed (not used)

However we have to do more tests as you already recommended.
On the next step I'm going to do extended tests also with other web browsers.

#223 Updated by Constantin Asofiei over 10 years ago

Marius: what happens when the browser is closed? Does the P2J client process get shut down properly?

#224 Updated by Marius Gligor over 10 years ago

This feature isn't yet implemented but will be. We have a notification on web sockets when the connection is closed on browser.
My intention is to implements here the code to terminate the client process.
Also other changes I have to made which are currently in my short list.
For example on CTRL-C the ClientCore#start loop is executed again and we should avoid to reinitialize the embedded server.
I did some temporary changes inside ClientCore class which allow me to run the client directly.
Also temporary the server embedded port is fixed to 5555 instead a random value.
That's why the ClientCore in not yet in my attached archive.
Finally I have to tests the entire flow by eliminating all temporary changes.

#225 Updated by Greg Shah over 10 years ago

Code Review 0115a

I am generally fine with the changes. Minor feedback:

1. Some imports need to be shifted to *.

2. Please put a comment in tty.js that the socket module must be initialized before the keyboard module.

3. You can put the mouse code back into screen.js. I don't think it hurts anything to have it.

#226 Updated by Marius Gligor over 10 years ago

1. Today I converted the keys.p application and I implemented the keyboard codes conversion for p4g.
I observed that the ESC-KEY is equivalent to ALT-KEY.
On Linux it works fine but I have to do some changes to work also in Windows OS.
On Windows OS the CTRL and ALT key are repeatead by keeping the keys pressed on Linux not.

2. On Linux I tested using Firefox, Chromium and Chrome browsers:
- Firefox works perfect.
- Chromium works fine but the font base line in canvas context is calculated wrong and the characters are rendered in a wrong position! (see chromium.png)
- On Chrome doesn't work and I got an error:
WebSocket connection to 'wss://192.168.1.15:5555/ajax' failed: Could not decode a text frame as UTF-8.

3. On Windows I tested using Firefox, Chrome, Safari, IE and Opera browsers:
- Firefox works perfect with some minor problems on rendering on old versions works perfect.
- On Chrome does not work and I got an error like on Linux:
WebSocket connection to 'wss://192.168.1.15:5555/ajax' failed: Could not decode a text frame as UTF-8.
- Safary like on Chrome since is based on the same engine.
- IE 9 does not support web socket only from IE 10. I have no IE 10 on my workstation.
- On Opera works like on Chromium Linux.

4. In conclusion so far only Mozilla Firefox on Linux works perfect.
Unfortunately we have to do more works in order to support other browsers.

5. Another issue is related to fonts. In Web Browsers the fonts are rendered different than in Swing.
For example I'm using the monospace font which looks good but the Swing client does not recognize the font as monospaced!
Using monospaced font results in an ugly screen so in JS I changed the monospaced with monospace font (see monospaced.png).

6. When the user press CTRL-C on ClientCore#start a loop is executed. The current session is closed and another session is open.
This works fine with guest account but doesn't work with our temporary accounts. Possible solutions for this issue might be:
- Re initialize the application without to close the session.
- Use the initial credentials to reopen the session. In this case we have to keep the passwords for temporary accounts unchanged in cache.

#227 Updated by Marius Gligor over 10 years ago

On Chrome seems to be a bug when secured connection (WSS) with self signed certificate is used.
https://code.google.com/p/chromium/issues/detail?id=119793

#228 Updated by Greg Shah over 10 years ago

Code Review 0116a

Generally, I'm OK with the code. My only concern: the use of Runtime.getRuntime().halt(statusCode) seems a bit "heavy handed". What is the reason it is needed?

#229 Updated by Greg Shah over 10 years ago

Using monospaced font results in an ugly screen so in JS I changed the monospaced with monospace font (see monospaced.png).

It is fine to have different defaults for the JS and Swing clients.

In regard to the cross-browser compatibility issues, please limit the effort you put in on that right now. For the near term, we are only going to support very recent versions of:

Firefox
IE
Safari
Chrome

Opera, mobile browsers and older browsers will be out of scope for now.

As to the versions of the above which we will support, we will pick those based on our requirements. So long as we limit our requirements to pretty common features (html5 canvas, html5 audio tag, websockets...), then we will be able to pick reasonable versions that already exist. But it may be needed to use very recent versions of these browsers (e.g. IE 10).

Our customers won't be rolling this out til late this year or next year at the earliest. I don't want to spend time on support for browsers that won't even be heavily installed at that time. Our customer has already agreed to this.

#230 Updated by Marius Gligor over 10 years ago

1. I found another way to terminate the client process on connection close. Just simple put an END-ERROR key (code 304) on the keyboard queue.
Doing so I force the ClientCore.start loop to exit and finally to terminate the client process.

2. I found two pieces of code SwingKeyboardReader.java and Key.java which helps me to implements the p4g keyboard mapper in java script.
Today I redesigned the JS keyboard code accordingly. On the next step I have to do more tests and compare the results by running keys.p
So far according to my tests seems to be OK on almost all cases.

3. I did also more tests on Windows OS:

- I tested with Firefox on Windows 8.1 and it works almost perfect. Very small problems on rendering.
- I tested with IE 11 on Windows 8.1 and it works fine. Just some small rendering problems.
- I tested with Chrome 31 and it works! The same observations like on IE 11.
- After updated to Chrome 32 (the latest release) it doesn't work ;-(
I updated the jetty library to the latest stable release (jetty-9.1.1.v20140108) and I tried again with Chrome 32
Unfortunately the same results, doesn't work. Finally I reported the issue to Google Chrome team.

#231 Updated by Constantin Asofiei over 10 years ago

Marius Gligor wrote:

1. I found another way to terminate the client process on connection close. Just simple put an END-ERROR key (code 304) on the keyboard queue.
Doing so I force the ClientCore.start loop to exit and finally to terminate the client process.

I don't think this will work. The application logic can catch the raised ERROR condition and choose to ignore it. The fact that the client ends in your case is just a coincidence specific to that program. To catch the ERROR condition, use something like:

do on error undo, next:
  /* main logic goes here */
end.

LE: the code needs to ignore, leave.

#232 Updated by Greg Shah over 10 years ago

I checked in the TerminalMenu class and we have this code for the Exit menu item:

      if (container.canExit())
      {
         exit.addActionListener(new ActionListener()
         {
            public void actionPerformed(ActionEvent evt) 
            {
               sim.quit();
               System.exit(0);
            }
         });
      }

Go ahead and use System.exit() to cause the process to halt.

#233 Updated by Greg Shah over 10 years ago

Code Review 0117a

The changes look fine except for the END-ERROR approach already discussed above.

The use of System.exit() should result in the session termination anyway, so I don't think you need the session object to be there.

Obviously, there is still some work to go with getting the restart behavior right on a CTRL-C, but it is shaping up nicely.

Good stuff!

#234 Updated by Marius Gligor over 10 years ago

System.exit(code) works fine.

#235 Updated by Greg Shah over 10 years ago

Code Review 0116b

The code looks good. Does ChuiWebSimulator really need to call super.quit()?

#236 Updated by Marius Gligor over 10 years ago

1. I did more investigations regarding the Google Chrome web browser.
Both Google Chrome and jetty teams works currently to implements WebSockets extensions.
Google Chrome is ahead and the latest release is not compatible with the current jetty implementation.
Here is a snapshot representing the handshake between browser and server.

Request URL:wss://192.168.1.15:5555/ajax
Request Method:GET
Status Code:101 Switching Protocols
Request Headers CAUTION: Provisional headers are shown.
Cache-Control:no-cache
Connection:Upgrade
Host:192.168.1.15:5555
Origin:https://192.168.1.15:5555
Pragma:no-cache
Sec-WebSocket-Extensions:permessage-deflate; client_max_window_bits, x-webkit-deflate-frame
Sec-WebSocket-Key:J23OCuuwjQ4i/evCnDnNog==
Sec-WebSocket-Version:13
Upgrade:websocket
User-Agent:Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36

Response Headersview source
Connection:Upgrade
Sec-WebSocket-Accept:S/YgcUNrTi05FKJdi+l0aO6PkJo=
Sec-WebSocket-Extensions:permessage-deflate, x-webkit-deflate-frame
Upgrade:WebSocket

As you can see Google Chrome already implemented the client_max_window_bits WebSocket extension but jetty not.
So we have to wait until all WebSockets extensions will be implemented by jetyy and than upgrade our library.

2. I did some small changes inside the java script screen module. I fixed all issues related to canvas rendering.
Fortunately the old Chrome 31 release remains on my Windows installation and can be used for tests.
Now the applications looks good on all major web browser Chrome, Firefox, IE, Opera.
I cannot do tests using Safari on Windows because the version installed on my laptop does not work like the new Google release.

3. Currently I'm working to implements the keyboard interface in JS.
In JS we have 3 events related to keystrokes which are fired in the following order:

keydown
keypress
keyup

The keypress event is fired only for keys mapped to printable characters not for modifier keys (CTRL, ALT, SHIFT).
This event provide the printable character code for the key pressed.
Also the event object does not contains any information related to CTRL ALT and SHIFT keys
because if keys like CTRL, ALT or SHIFT are pressed the keypress event is not fired at all.

The keydown event is fired on all keys but unfortunately we receive a key scan code not the mapped printable character code.
Even more not all the key scan codes are the same across the web browsers. (see http://www.javascripter.net/faq/keycodes.htm)
The mapping between scan codes and character codes depends on the keyboard layout (US, EN, RO etc.)
So I think that we have to design and implements also a key mapper for a US keyboard layout inside the JS code.
Please let me know your opinion related to this issue.

#237 Updated by Greg Shah over 10 years ago

Please document the differences between Java/Swing key processing and the Javascript equivalent. It was my understanding that the two models were pretty close. And I thought that the event.shiftKey, event.ctrlKey, event.altKey and event.metaKey properties were defined on the keypress event object. Is that incorrect?

If those modifier properties are there, then it seems we could leverage our SwingKeyboardReader code. Perhaps we can simulate the Java events using the JS input and let the SwingKeyboardReader code handle the processing on the Java side.

#238 Updated by Marius Gligor over 10 years ago

1. Today I did the appropriate changes inside the JS code in order to manage the keyboard events.
So far all my tests reveals the same results using keys.p on Swing emulator and JS code emulator.
Please take a look over my attached code.

2. In JS we have all modifiers keys inside the event object but on the keydown event not in keypress event.
In keydown event we have only the key scan code not the printed character code.
They are no ways to convert the scan code to character code even using JS tools like Dojo, jQuery etc.
On my JS code I made a conversion from scan code to character code only for printable keys inside the keydown event.
Doing so I'm able now cto ompute the key codes for combinations like CTRL and/or ALT.
The key map is done for a US keyboard layout.
Unfortunately JS is much more restricted and limited than Java.

The keypress event is used to process the printable characters which are available on the event object.
I'm not sure because I have only US keyboards on my computers, but processing the special characters on keydown
and printable characters on keypress event could solve also the keyboard layout issue.

3. In my opinion trying to leverage the use of SwingKeyboardReader require more work effort and I'm not sure if it finally works.
The keyboard events in Java and JS are different as I already described in paragraph 2.

#239 Updated by Marius Gligor over 10 years ago

  • File mag_upd20140117b.zip added

Here is an improved version for keyboard management. Seems to be closed to the final solution.
Nevertheless I will do more tests.

#240 Updated by Greg Shah over 10 years ago

I have not gotten to look at your latest code yet.

However, I think you have come to the wrong conclusion in regard to the keypress event. You can definitely get the state of the modifier keys. Please try out this code:

<html>
   <head>
      <title>keypress Event Tester</title>

      <script type="text/javascript">
         keyHandler = function(event)
         {
            text = "keypress event:<br>";

            for (attr in event)
            {
               text = text + attr + " = " + event[attr] + "<br>" 
            }

            element.innerHTML = text;
         }

         var element;

         if (window.addEventListener)
         {
            window.addEventListener("load", function()
            {
               document.addEventListener("keypress", keyHandler, false);
               element = document.getElementById("something");
            }
            , false);
         }
      </script>

   </head>
   <body>
      <div id="something">Type keys!</div>
   </body>
</html>

You can see that there are Boolean properties for each of the modifier keys. As far as I can tell, we should be able to use keypressed for the printable keys (eliminating the layout issues) and use keydown for non-printable stuff, which is just like we do in Java.

Perhaps the Dojo event processing is obscuring this? I deliberately just used basic JS for this example. I have only tested it in Firefox, but it works great there.

#241 Updated by Greg Shah over 10 years ago

By the way, you re-uploaded mag_upd20140117b.zip when I think you intended to upload mag_upd20140121b.zip.

#242 Updated by Marius Gligor over 10 years ago

  • File deleted (mag_upd20140117b.zip)

#243 Updated by Marius Gligor over 10 years ago

#244 Updated by Marius Gligor over 10 years ago

I Tested your code and here are my conclusions:

1. The code does not work at all on IE (Windows) web browser.
The event keypress is fired only for keys mapped to printable characters.
For example if CTRL or ALT key is pressed the event keypress isn't fired.

2. The code does works partial on Chrome (Linux and Windows).
- If CTRL key is pressed works only if event.defaultPrevented = true. For example it's works for CTRL-Q but does not work for CTRL-A, CTRL-B, CTRL-S etc.
- If ALT key is pressed the event is not fired.

3. The code does not work on Safari (Windows).
The event keypress is fired only for keys mapped to printable characters.
Foe example if we press CTRL-Q the browser is closed because on CTRL-Q is mapped to exit command.

4. On Firefox on the other hand it's works indeed but with a small change.
In the original code when you press CTRL-S for example the keystroke also open the browser "Save AS" dialog.
In order to prevent this behaviour we have to avoid the browser action execution associated with the event by adding the following line: event.preventDefault()
With this changes in place the code works as we expected.

<!DOCTYPE html>
<html>
<head>
<title>keypress Event Tester</title>

&lt;script type="text/javascript"&gt;
keyHandler = function(event) {
event.preventDefault();
text = "keypress event:&lt;br&gt;";
for (attr in event)
{
text = text + attr + " = " + event[attr] + "&lt;br&gt;"
}
element.innerHTML = text;
}
var element;
if (window.addEventListener)
{
window.addEventListener("load", function() {
document.addEventListener("keypress", keyHandler, true);
element = document.getElementById("something");
}
, false);
}
&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;div id="something"&gt;Type keys!&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;

5. The event object structure is different on each web browsers. It contains common fields as well as custom fields specific to each web browser.
Another important difference between JS and Java: CTRL key in not processed in JS.
We have only the key code or char code for the key and a flag which store the CTRL key status (true - pressed, false - not pressed)

6. In conclusion the Firefox behaviour on keypress event seems to be a really useful solution for our purposes.
Unfortunately cannot be used for a cross web browsers solution.

#245 Updated by Marius Gligor over 10 years ago

I did some small changes and improvements.
Also I tested a version using native JS instead Dojo. The results are the same.

Doing tests I found some interesting behaviours running on Linux (Kubuntu):
Some keystrokes like ALT+F4, ALT+F2 are processed by OS and are not available to applications!
I obtained the same behaviour running the ChuiSwing client and your JS example code:
-On ALT+F4 the application windows is closed.
-On ALT+F2 the KDE search window is open.
Doing tests in Windows OS this not happens.

My current JS implementation works exactly like ChuiSwing simulator when I executed key.p application.
So far I found no differences but nevertheless might be since testing all possible combinations took a while.

#246 Updated by Greg Shah over 10 years ago

OK, these are good findings.

Please review the DOM level 3 events specification: http://www.w3.org/TR/DOM-Level-3-Events/#event-type-input

I wonder if the keydown can be used for most processing but then for the printable chars the corresponding input event can be used.

The issue here is simply that we will constantly be having to add support for new keyboard layouts if we cannot find a better solution. I assume the customer uses a UK English layout. We have other potential customers around the world that will certainly have other layouts in use. Now if the time to see if we can find a more elegant solution. Obviously, I prefer this to be completely in JS.

But certainly we do know that the Java Swing code has already solved this issue. I don't just mean our code in SwingKeyboardReader. I mean that the Java platform solves this problem. Is there a way to leverage this? For example, if we detect the locale/layout in JS and then match that with the same locale in Java, we can force it into a Swing component using component.getInputContext().selectInputMethod(locale). From there we can potentially use that component to process the low level events that we create OR we can dynamically create a map (of low-level to high-level transformations) which can be sent down to the JS code.

#247 Updated by Marius Gligor over 10 years ago

1. The DOM level 3 events specifications are implemented total or partial on all major web browser but sligtly different.
Using your JS code which is really a valuable tool I did a lot of tests on different web browsers for keydown and keypress events (see the attached files)
The conclusion is that we cannot get the character code of a key using the keydown event.
We have to combine the keydown event and key press event as follow:

- On keydown we have to handle only the control keys, that is, keys having non printable characters mapped.
This works fine even for diferent layouts. I tested a QWERTZ DE (German) layout keyboard and a US QWERTY layout keyboard.
Pressing CTRL+Y and CTRL+Z for example works fine even thinking that the position on keyboard differ on US and DE layouts.

- On keypress we have to handle only keys having printable characters mapped.
In this case the charCode of event object contains the Unicode value for the character which is mapped to key in format UTF-16.
Example: 8364 (U+20AC) which correspond to EURO sign. The question here is: Does p4g process Unicode UTF-16 characters?

2. A keyboard could have many layouts associated with locales. Even a US key has more than one layout: US standard and US International layout (see the attached picture)
The layouts except the US Standard extends the keyboard capabilities by adding more characters on some keys (3 or 4)
In this case the ALT key from the right is used as a control key which in combination with another key generate the character code.
This special key is called ALTCHAR key. Example ALT-CHAR+E on a DE layout will get 8364 (the EURO sign)
Even more the ALTCHAR key could be combined using the SHIFT key like ALTCHAR+SHIFT+E and allow to define up to 4 characters / key.
Using the DOM level 3 events specifications according to my researches the the ALT key from the right works as follow:

- On the US Standard layout only for a keydown event the event object has the following properties:
altKey = true
ctrlKey = false
location = DOM_KEY_LOCATION_RIGHT (2)
keyCode = ALT (18)
- On all other layouts which extends the keyboard capabilities:
altKey = true
ctrlKey = true
location = DOM_KEY_LOCATION_RIGHT (2)
keyCode = ALT (18)
In conclusion the location and keyCode define the ALT key from the right (we have 2 ALT keys on a keyboard, one on the left and one on the right)
The ctrlKey specify the cope of the key. If ctrlKey=false means normal if ctrlKey=true menas that the key is used as ALTCHAR.

As a result of my researches I implemented and tested the keyboard layout handle in JS.
Now the code works fine using different layouts like US International, English UK, German DE, Romanian RO, etc.
I did all the tests on Windows OS only using IE, Chrome, Firefox, Safari and Opera web browsers.

The only problem that I found is in Safari which does not set the location field on event object.
The last release 5.1.7 for Windows Safari is dated May 9 2012 and think that we could consider that Safari for Windows is obsolete.
The same observation for Opera, 2012 is the year of the latest release!

#248 Updated by Greg Shah over 10 years ago

Code Review 0123a

This is really, really good! I'm glad that you were able to work out an approach that is much more generic. Well done.

My only question: Should mapUSkeys() be renamed to something else, since this is no longer a US-only solution?

Answers to your questions:

The question here is: Does p4g process Unicode UTF-16 characters?

It can. But more importantly, when we are operating in Java we DO process using Unicode. So it is perfectly valid to have the JS code deal entirely in Unicode.

Where we output something that can have different codepages, we have to write code to handle that (to convert the Unicode data into the codepage-specific output). Likewise, where we read input that can come in multiple codepages, we must have code that is aware (so that it can translate from the input codepage into Unicode). If everything is already in Unicode, no translation is needed.

The only problem that I found is in Safari which does not set the location field on event object.
The last release 5.1.7 for Windows Safari is dated May 9 2012 and think that we could consider that Safari for Windows is obsolete.
The same observation for Opera, 2012 is the year of the latest release!

Old releases are not a problem. We aren't planning to support Opera at all right now and for Safari, we can choose a much newer version that does work.

#249 Updated by Marius Gligor about 10 years ago

I did some small improvements and bug fixing. Basically I eliminated the dojo/keys module dependencies.
With this changes in place it's very easy to eliminate all the Dojo dependencies.
I found no differences using Dojo (a) or native (b) code, so I think that at this stage we could use the jS native code version for CHUI.
Please let me known your opinion regarding this issue.

#250 Updated by Greg Shah about 10 years ago

Code Review 0124a

All the changes look good. I like the more modular approach.

Code Review 0124b

I prefer the non-Dojo version. My only question: does the document.addEventListener() work properly on IE (at least older versions didn't have it). If it is working properly there, then let's go with the non-Dojo version for now. Good stuff.

#251 Updated by Greg Shah about 10 years ago

Can you please post a list of what is left to do on #2212?

#252 Updated by Marius Gligor about 10 years ago

1. We have to solve the CTRL-C issue to initialize the converted application without a new server authentication if possible.
2. I have to do some tests with IE and will be back to you.

#253 Updated by Marius Gligor about 10 years ago

It's works in IE but I have to fix a small issue. Event preventDefault() does not work properly with IE.
I tested both versions Dojo/Native and the problem is the same.
I'm going to fix this issue.

#254 Updated by Marius Gligor about 10 years ago

Some small changes to fix ESC key in IE.

I did more tests on Windows using IE 11 and Chrome 31.
Unfortunately on both web browser does not work properly ;-(
The short-cuts keys are always processed by the web browser.

For example CTRL+N in Chrome always open a new Window, CTRL+T a new tab page.
On IE ALT+H goes to Help option on menu bar, ALT+F4 close the application.

Firefox seems to be the only web browser which works properly (as we need).
I will try to find a solution if exists for IE and Chrome but seems to be a big challenge.

#255 Updated by Greg Shah about 10 years ago

To disable default behavior you may have to do 2 things:

1. Prevent the default behavior. In browsers that support addEventListener() there is the event.preventDefault() which does this. IE in versions prior to IE9 used event.returnValue = false to cause the same result.

2. Stop the event from "bubbling" or propagation. The idea is that there may be other event handlers registered on the current DOM element or on one of the parent DOM elements. Unless you take some action, these other event handlers will be executed. In browsers that support addEventListener() there is the event.stopPropagation() which stops invocation of any event handlers registered on the PARENT DOM elements. Prior to IE9, IE had event.cancelBubble = true to achieve the same thing.

But the new DOM Level 3 Events supports a event.stopImmediatePropagation() which is even better because it stops invocation of additional handlers that are registered at the current DOM element (not just those registered at the parent DOM element).
Have you tried event.stopImmediatePropagation()?

One other idea: instead of registering for key events at the document level, I wonder if registering at the canvas may be better for cancellation purposes. On the other hand, it may create conditions where some events are not delivered if the keyboard focus is somehow placed outside of the canvas (e.g. with a mouse click).

#256 Updated by Greg Shah about 10 years ago

Code Review 0124c

My only thought here is that we probably need to always preventDefault() and stopImmediatePropagation() for every event, since these events are meant to be consumed by us. For now, you only do this when you detect the modifier keys.

#257 Updated by Marius Gligor about 10 years ago

Starting from IE 9 preventDefault() is supported. We are interested on IE 10 and IE 11 because IE 9 does not support WebSockets.
In my code I'm using both preventDefault() and stopImmediatePropagation() but with no effect.
Anyway during this weekend I will try to find a solution.
One idea is to use a pop-up window for our JS simulator.
I saw that when a pop-up window is created we can eliminate menu bars and toll bars.
Do you believe that could be a solution?

#258 Updated by Greg Shah about 10 years ago

I saw that when a pop-up window is created we can eliminate menu bars and toll bars.
Do you believe that could be a solution?

Possibly.

I have been considering this approach in general, as it has some other advantages. But I am worried about the disadvantages that may come along with this. Popup blocking configurations in the browser can interfere and there may be other issues. Please analyze the pros/cons of this approach and document them here.

#259 Updated by Marius Gligor about 10 years ago

Looking for a solution to fix the cross browser keystrokes handle issues I found:

Supported Key Combinations:
The actual possible key combinations vary greatly between browsers, as most browsers have a number of built-in short-cut keys, which can not be used in web applications.
For example, Mozilla Firefox allows binding almost any key combination, while Opera does not even allow binding Alt short-cuts.
Other browsers are generally in between these two.
Also, the operating system can reserve some key combinations and some computer manufacturers define their own system key combinations.
If the browser handles the short-cut key automatically and does not pass the event on there is really not much we can do to fix it.

Indeed IE and Chrome define a set of reserved keys which cannot be override. We have no way to prevent their actions!

1. IE - Most of the reserved keys are ALT+key combinations but also CTRL+key combinations exists.
The actions assigned to the reserved keys are always executed and the keystrokes are sent also to JS code with few exceptions (ALT+F4 close the browser).
Here: http://connect.microsoft.com/IE/feedback/details/778745/default-browser-behavior-for-alt-key-shorcuts-can-be-overridden-by-the-accesskey-attribute-but-not-by-scripts
the Microsoft IE team advice us that we cannot override the browser default due to a design constraint.

2. Google Chrome define also a set of reserved keys (short-cuts) that cannot be override.
Even more some combination like CTRL+N, CTRL+T never reach the page JS code in other words we never receive events for this keystrokes.
Goolge Chrome could be lunched in a kiosk mode. On this mode we have control over Fn keys and ALT+Fn keys.
ALT+F4 never close the window but CTRL+N, CTRL+T, etc. cannot be ever controled.
The worst case ever CTRL+W close the Chrome browser on both modes.

3. Mozilla Firefox is the only web browser which allow us to control the keyboard keystrokes almost 100%.
So far I found no keys combinations which could not be processed in JS and prevent default behaviour.
In Windows using Firefox 26 (the latest release) works perfect with our JS simulator.
In Kubuntu Firefox 26 works the same except for some key combinations which are reserved by the OS
(ALT+F2 open the search window, ALT+F3 open window menu, ALT+F4 close the application main window).
Fortunately we have the ESCAPE+Fn key combination as an alternative.

4. My idea was to use a modal pop-up window hoping that we have full control over all keyboard events.
Unfortunately the assertion is not true and the actions assigned to reserved keys cannot be prevented.

5. In conclusion only the Firefox (26) seems to offer us the entire control over keyboard events.
The question is: Will Firefox offer this capabilities also in the future?
It's no reason to spend more time searching for a solution to use IE and Chrome because we have no solution!
Is a huge frustration to see these constraints that can not be fixed in any way!

#260 Updated by Marius Gligor about 10 years ago

The keyboard listeners are bind on canvas instead document.
The canvas should have the focus in order to receive keyboard events. On start the canvas is the focus owner.
It's works basically like previous version.
The problems with IE and Chrome reserved keys remains the same.

#261 Updated by Greg Shah about 10 years ago

Code Review 0125a

I don't want to remove all the server-side Dojo support that you already provided. Put that work back in so that when the time comes for us to use Dojo features, we will be able to easily do so.

About the unavailable keys, don't worry. It was on my list as a potential issue that might not be resolved. We will document this set of limitations and customer's can choose browsers knowing the limitations they will have to deal with.

With enough effort, we could solve this problem. But it would require a non-trivial bit of native code for each platform we would support (and probably some native code for each browser too). For example, most platforms provide an architected hook interface to intercept messages before they are delivered to the application's specific queue. On Windows:

http://msdn.microsoft.com/en-us/library/windows/desktop/ms632589%28v=vs.85%29.aspx

Since most (nearly all) GUI implementations are based on a message-passing design, this trick is something that is commonly available. But it is NASTY and FRAGILE. And in the end it is not our responsibility to make up for the design decisions of the browser developers.

At this time, just make sure you document the list of browser-specific key handling findings so that we don't have to do more research later.

#262 Updated by Greg Shah about 10 years ago

Code Review 0125b

I like this version better from the perspective of how you made the code better separated.

But since the canvas approach doesn't solve any real problems for us AND it does add a new focus problem, I think that it is best to go back to registering the handlers on the document.

#263 Updated by Marius Gligor about 10 years ago

  • File mag_upd20140125c.zip added

Done.

#264 Updated by Greg Shah about 10 years ago

Code Review 0125c

I like it. The only thing I don't understand is why the tty.keymap.js is missing.

#265 Updated by Marius Gligor about 10 years ago

  • File deleted (mag_upd20140125c.zip)

#266 Updated by Marius Gligor about 10 years ago

Sorry, was my fault.

#267 Updated by Greg Shah about 10 years ago

It is good, thanks.

#268 Updated by Marius Gligor about 10 years ago

1. Today I did a code review over the JS code. The changes are attached.

2. Also I started to investigate the CTRL+C issue. The behaviour is done by the stop_disposition condition value get from registry:

<node class="integer" name="stop_disposition">
<node-attribute name="value" value="1"/>
</node>

Three values are possible:

0 - retry without logging off
1 - force log-off and then re-logon (default)
2 - force log-off

Values 0 and 2 works fine without any changes.

For value 1 however we have to do some changes. I think that we have two options.

A. Keep the temporary account unchanged and reuse the same credentials on re-logon. I tested and this works fine.

Advantages:
- Easy to implements.
- Very fast. On systems having big directory file the temporary accounts are changed only at server start-up.
No changes are made after start-up so no time is consumed to update the directory file whenever a temporary account is changed.
Disadvantages:
- Less secured. The temporary accounts are changed per server session, that is, only at server start-up.

B. Another solution is to use the existing RemoteObject to get a new account and do a re-logon. This scenario should be implemented.

In other words the first authentication is done using the environment variables values as currently is already implemented.
Than we have a valid session and a valid RemoteObject interface.
We could add a new method to this interface in order to provide a temporary account from server pool remotely.
On the client we can get a new account before the actual session is closed and use this accounts to do a re-logon.
Advantages:
- More secured.
Disadvantages:
- Require more changes to add this feature.

Is any time-out value set for RemotObjects sessions?

#269 Updated by Greg Shah about 10 years ago

B. Another solution is to use the existing RemoteObject to get a new account and do a re-logon. This scenario should be implemented.

How about a slight variation on this? We already return a boolean from the standardEntry() method. Let's just return back an object that contains both the boolean value and any re-logon/authentication info needed to re-establish the next session. That way, it minimizes changes.

Is any time-out value set for RemotObjects sessions?

The "original" temporary session was disconnected, so it no longer will work. But at the time standardEntry() returns, there is still a valid session and the data we would obtain via environment var can be just returned back using that existing session.

#270 Updated by Greg Shah about 10 years ago

Code Review 0127a

I am fine with the code. It looks very good.

My only request is for tty.keyboard.js, tty.keymap.js, tty.screen.js and tty.socket.js. Please put back (or add if not already there) blank lines that separate "member" variables. For example:

   /* exports object */
   var my = {};    
   /* track escape status */
   var escape = false;                     
   /* track alt char status */
   var altChar = false;

should be:

   /* exports object */
   var my = {};    

   /* track escape status */
   var escape = false;                     

   /* track alt char status */
   var altChar = false;

#271 Updated by Marius Gligor about 10 years ago

  • File mag_upd20140128a.zip added

Here is the implementation for CTRL-C issue and some remarks.

1. A Result object is used instead a boolean value.
2. The temporary account credentials are stored into Result object only in case of re-logon and a web client.
3. In case of a re-logon the temporary account is locked until the client will call again the main entry when the account is unlocked, that is, returned back to pool.
Also as a protection whenever a client call the main entry point the locked accounts list is checked
for so called "old accounts", accounts that are enabled from a specified amount of time (more than 10 seconds)
and not returned back to pool. This accounts are also unlocked. Please see my javadoc remarks.
4. WebSockets also define a time out value. If no information is sent over the web sockets for an amount of time the connection is closed.
This is an annotation value please see the ChuiWebSimulator class annotations.
I had set a value of 900000 which means 15 minutes.

#272 Updated by Greg Shah about 10 years ago

Code Review 0128a

1. In ChuiWebSimulator, I would like to shift to an idle timeout (maxIdleTime) that is set programmatically instead of via an annotation. Using the 15 minute as a default is fine. Please add a mechanism to override the default using configuration in the directory.

2. The Result class is missing from the update.

3. In StandardServer, I am not convinced that the use of a ConcurrentHashMap for lockedAccounts is sufficient to make this code thread safe. There is too much editing of the contents from multiple locations for this to be safe. Please use synchronization to make this safe.

4. The TemporaryAccount now has state that is edited in multiple places. That class needs to be properly synchronized.

5. The MainEntry javadoc should specify that the uuid parameter is optional (and thus it may be safely passed as null) unless this is a web client.

#273 Updated by Marius Gligor about 10 years ago

  • File deleted (mag_upd20140128a.zip)

#274 Updated by Marius Gligor about 10 years ago

I added the missing Result class.
The ConcurrentHashMap is fully thread safe by design and is internally synchronized.
The Java concurrent package is special designed to avoid the external synchronization.

#275 Updated by Greg Shah about 10 years ago

The ConcurrentHashMap is fully thread safe by design and is internally synchronized.
The Java concurrent package is special designed to avoid the external synchronization.

Yes, I'm aware of this. My point is that it is only sufficient for some use cases. Where you are potentially adding/removing/editing contents from multiuple locations on multiple threads, it is not always safe. In this case, the things that make me nervous:

1. lockAccount() and unlockAccounts() are not atomic.
2. There are multiple remove points for the map.
3. The uuid is provided by the client, if there is ever a scenario where the same uuid is accidentially (or maliciously) reused within a short interval of time, the results would probably break the logic since it would be possible to replace a TemporaryAccount instance that is already there and which is not closed. If that were to happen, that temporary account would never be disabled and it wouldn't timeout. It could be used over and over to access the server.

I think this is a case for proper synchronization.

#276 Updated by Marius Gligor about 10 years ago

  • File mag_upd20140128b.zip added

1. Done. ChuiWebSimulator.onConnect allow us to set the session idle time out which in turn override the default value.

2. Done.

3. Done. I understood what you said about this issue and is true. The lock and unlock account static methods are now synchronized and by consequence atomic.

4. Sorry, but I don't find any place in this class to use synchronization.
The open and close methods are synchronized in TemporaryAccountPool class.
Other places?? Could you help me please with a short hint?

5. Done.

#277 Updated by Marius Gligor about 10 years ago

  • File deleted (mag_upd20140128b.zip)

#278 Updated by Marius Gligor about 10 years ago

  • File deleted (mag_upd20140128b.zip)

#279 Updated by Marius Gligor about 10 years ago

Sorry, I changed the date of last changes on the ClientBuilderOptions class header.

#280 Updated by Greg Shah about 10 years ago

Sorry, but I don't find any place in this class to use synchronization.
The open and close methods are synchronized in TemporaryAccountPool class.
Other places?? Could you help me please with a short hint?

TemporaryAccount instances are created on one thread (in TemporaryAccountPool), updated on other threads (in UpdateAccountTask) and read on other threads (from StandardServer)...

This means it is unsafe to share the 3 members of the class (subject, password and timeout) without synchronization. Make sure that any access to those members are done from synchronized methods. It doesn't solve all access problems, but it ensures that the data being read/written is done atomically.

#281 Updated by Marius Gligor about 10 years ago

I did the changes. Normally no concurrency should occur on this class but just for safety is OK.

I have two other questions please:

1. We are not use Dojo at this stage. Should I provide the Dojo jar dependencies on lib folder or we will add this jar later?

2. I have a new build for jetty 9 the latest available. Should we update the jetty jar file or we have to wait until the final release.
NOTE: When I did the new build for jetty the name of the jar remains the same but is a little bit more big in size.

#282 Updated by Greg Shah about 10 years ago

Code Review 0128c

The code looks good.

but just for safety is OK.

Exactly.

1. We are not use Dojo at this stage. Should I provide the Dojo jar dependencies on lib folder or we will add this jar later?

Go ahead and add this now so that it is easy to use later.

I have a new build for jetty 9 the latest available. Should we update the jetty jar file or we have to wait until the final release.

Update it now.

#283 Updated by Marius Gligor about 10 years ago

Here is the package that I tested today. The package contains the new jetty 9.1 build.
Due to it size the Dojo zip file has been uploaded at the following location:

~/shared/projects/p2j/dojo/dojo-release-1.9.2.zip

The CTRL-C part of the regression tests FAILS and I have to retry this part.
The main part of the regression tests passed.

#284 Updated by Greg Shah about 10 years ago

Code Review 0129a

There are no concerns. Please note that I don't plan to check in the Dojo build, into bzr. I assume that it can be present or not without any errors (unless we try to use it and it isn't there). Is that correct?

Please continue getting this tested.

#285 Updated by Greg Shah about 10 years ago

For #2238: the following are some potential issues that we need to consider/research. Document your thoughts and findings here. If they are in fact real issues, then work to resolve them.

  • What happens when the user forces the browser to exit (without exiting the converted app first)? We would want to make sure that we get a notification when this happens, so that we can tell the server-side to exit.
  • Can we disable the browser's context menu?
  • How do we handle the use of the browser's FWD and BACK buttons (and history in general)? Can we disable those?

#286 Updated by Marius Gligor about 10 years ago

The Dojo dependencies zip file is always in the CLASSPATH because is in the list of dependencies inside the manifest file.
The build of p2j is not affected if the presence of Dojo zip file in lib directory.
Also at runtime as long as Dojo dependencies are not use no problems occur.

#287 Updated by Marius Gligor about 10 years ago

When the page having the JS code running is loaded a WebSocket connection is open.
If the user close the browser the WebSocket connection is closed and the server is notified.
On close notification the client exit and the process is terminated.
Also once the WebSocket connection is open we have an idle time out defined.
On time out also the client force an exit and the process is terminated.
However if from vary reasons the WebSocket is not open (the page is not loaded, WebSocket are not supported, JS disabled, others) the process could remains on running state for an indefinite period of time.
Maybe for safety it's a good idea to design and implements something like a "watchdog" timer used to
force an exit and terminate the client if no WebSocket connection is open during a specified amount of time.
What do you think about this?

#288 Updated by Greg Shah about 10 years ago

Maybe for safety it's a good idea to design and implements something like a "watchdog" timer used to force an exit and terminate the client if no WebSocket connection is open during a specified amount of time.

It is an excellent idea. Implement this, but don't include it in the current update. I want to commit the 0129a update as it is (assuming it passes testing).

#289 Updated by Greg Shah about 10 years ago

The Dojo dependencies zip file is always in the CLASSPATH because is in the list of dependencies inside the manifest file.

You mean it is implicitly in the CLASSPATH because of the manifest file? But there are no negative effects for the actual file being missing (unless we change our code to use Dojo).

#290 Updated by Marius Gligor about 10 years ago

To avoid any confusions maybe is better to eliminate the Dojo zip from CLASSPATH (from manifest file).
We could add this later when we effectively use the Dojo dependencies.

#291 Updated by Greg Shah about 10 years ago

To avoid any confusions maybe is better to eliminate the Dojo zip from CLASSPATH (from manifest file).

No, it is OK to leave it. I just wanted to confirm that my understanding was correct.

#292 Updated by Marius Gligor about 10 years ago

After three executions of CTRL-C part regression tests I obtained the same results (see attachments).
The main part of the regression tests passed.
The server.log file contains some strange messages related to persistence layer.
Example:

[01/30/2014 06:21:23 EST] (com.goldencode.p2j.persist.trigger.DatabaseTriggerManager:INFO) DatabaseTriggerManager registered with TransactionManager.
[01/30/2014 06:21:27 EST] (com.goldencode.p2j.persist.lock.InMemoryLockManager:WARNING) [00000016:0000005F:syman] --> local/majic/primary: cleaning up 1 leaked record lock(s) for exiting context ({temp_sum:T=SHARE [syman:00000016

#293 Updated by Marius Gligor about 10 years ago

Regarding browser session and JS code.
The web browser are designed to navigate over the Internet and for security reasons have a lot of restrictions.
Most of them are related to JS code which directly interact with browser environment.

1. The browser context menu can be controlled from JS on all major web browsers IE, Firefox and Chrome:

// disable browser context menu
document.oncontextmenu = function () {
return false;
};
Using this piece of JS code the context menu will be completed disabled.

2. The navigation buttons on the other hand cannot be controlled from JS to be hidden or disabled.
However it is possible to control the navigation from JS in many ways:

a). By using the following JS code on the page which is the target of the back navigation:
// prevent back navigation
function noBack() {
window.history.forward();
}
noBack();
window.onload = noBack;
window.onpageshow = function(evt) {
if (evt.persisted)
noBack();
}
window.onunload = function() {
void (0);
}
With this JS code in place when the user press the back button the back operation is executed
but the JS code forward the request back to the page. (from B page back to A page and than from A page forward to B page)
b). A better solution is to open our TTY JS on a new page (pop-up window).
This require an intermediate html page something like a "splash" window from which the pop-up window is created and open.
Here we have also 2 options:
- Use JS and automatically create the pop-up window when the "splash" page is loaded.
In this case the browser pop-up blocker blocks the pop-up window.
The user should confirm that the pop-up window could be open.
- In order to avoid the pop-up blocker action we can put a link inside the "splash" window and when the user click the link
open the pop-up window. In this case the pop-up blocker is inactive, no action is take.
The problem on both cases is that we cannot close the "splash" window after the pop-up window is open. It's works
on Chrome but IE ask for a confirmation and Firefox do nothings. Nevertheless we can keep both windows opened and hide the link on the "splash" window.
On the pop-up window we can disable or hide a lot of objects like menu, context menu, scroll bars, etc.
The only object that cannot be eliminated from the pop-up window is the address bar.
I have also to do some tests in order to find out if it's possible to use short-cuts keys in pop-up windows to navigate back.
It is possible also to combine pop-up windows with JS code to avoid back navigation.

Currently whenever the user try to navigate back or reload (refresh) the page the web socket connection is closed and the client process is terminated.
The user should go to the authentication page and restart a new session.
A good idea is not to terminate the process when the user refresh the page and close the socket connection but instead we should use the "watchdog" timer.

On web sockets close - start (arm) the watchdog timer.
On web socket open - stop the watchdog timer.

In other words the exit statement should be executed only when the converted application is ended (F4 for example)
or by the watchdog timer if the web socket connection is not open after a specified amount of time.

#294 Updated by Greg Shah about 10 years ago

Please check in and distribute the 0129a update. Make sure to post the bzr revision number here.

#295 Updated by Marius Gligor about 10 years ago

The changes inside the mag_upd20140129a.zip archive has been committed to bzr revision 10457

#296 Updated by Constantin Asofiei about 10 years ago

Marius, I think there is a problem with the temporary accounts. If I understood properly, the process of authentication works like this:
  1. user accesses the https://localhost:<port>/chui, where the username/password for an existing account on the machine is needed
  2. if these credentials are valid on the machine, it will run the command to spawn a new P2J Client.
  3. the P2J Client will use the credentials read from the P2J_SUBJECT and P2J_PASSWORD environment variables (this will point to the temporary account) to authenticate. After control gets back to P2J Client, it will compute the redirect URI and return it back to the server, to redirect the user to that page.
Where are the problems:
  1. in case there is a custom authentication hook, this will be bypassed.
  2. after step 3, the Reader, Writer and Conversation threads remain on the context for the temporary account. This doesn't look right to me. Even when using the testcases project, the Conversation thread is still on the context for the temporary account. The correct account should have been the bogus account, specified at the configuration for the com.goldencode.p2j.security.GuestAccess auth plugin.

What we are missing is the implementation for this part from note 74 from Greg (can't tell from the comments if this is still WIP or not):

A better solution is to implement a temporary P2J session with the server. The idea: create one-time authentication credentials (some kind of random token/key) that can be passed (like our password info) via STDIO to the child process. If we can find a secure way to get it to the P2J client, the P2J client can use this to do a temporary connectDirect() to the server with those credentials, read the key store, initialize the web server and send back the URI. Then it would disconnect and drop into the normal processing. The major advantage of this approach is that it can be extended to the "remote client" scenario, which we will be implementing later. Another major advantage is that we plan to do something similar to allow batch mode P2J clients to be autostarted by the server.

The change is in ClientCore.start - this should connect to the server twice:
  1. using the temporary credentials, to start the web server. This needs to use a copy of the original BootstrapConfig instance, to not lose any explicit user/passwd passed via access:subject:id and access:password:user configurations (or other configuration).
  2. if web server was started, connect again. This time, fallback using the normal processing: use auth plugin as necessary, let the user input credentials, etc.

Finally, when you get a chance, please document all the rights needed for the server account (i.e. standard account for the test server or gso for MAJIC). Currently, I had to remove the NetResource, AdminResource and DireectorResource security plugins to be able to populate the temporary account pool. The default security plugins should be:

       <node class="strings" name="resource-plugins">
         <node-attribute name="values" value="com.goldencode.p2j.security.SystemResource"/>
          <node-attribute name="values" value="com.goldencode.p2j.security.AdminResource"/>
          <node-attribute name="values" value="com.goldencode.p2j.net.NetResource"/>
          <node-attribute name="values" value="com.goldencode.p2j.directory.DirectoryResource"/>
      </node>

but the testcases project has only the SystmeResource plugin - this is why adding the server account to the system/admin ACL worked for you.

#297 Updated by Marius Gligor about 10 years ago

OK. I have to think to this issue.

At this moment what I understood is as follow:

1. Use the temporary account to connect to the server, import the server key, start server and redirect user.
2. Close the temporary account session.
3. Create a new session this time using the credentials provided by the user. In other words the ThinClient
should be initialized using this session and the MainEntry should be executed also using this session.

What it's not clear for me. In case of a custom authentication plug-in where the user should enter the credentials?
The plug-in is like a p4g program which provide a login form on the terminal, in this case in the web page?

#298 Updated by Constantin Asofiei about 10 years ago

Marius Gligor wrote:

At this moment what I understood is as follow:
1. Use the temporary account to connect to the server, import the server key, start server and redirect user.
2. Close the temporary account session.
3. Create a new session this time using the credentials provided by the user. In other words the ThinClient
should be initialized using this session and the MainEntry should be executed also using this session.

You understood correct.

What it's not clear for me. In case of a custom authentication plug-in where the user should enter the credentials?
The plug-in is like a p4g program which provide a login form on the terminal, in this case in the web page?

Yes, in the web page; in MAJIC case, the first thing the user will see will be the MAJIC login screen. If the system credentials are the same as the P2J credentials (when user/pass is used), we could reuse that, but this would require changes in the authentication code (and the custom auth plugin probably).

#299 Updated by Marius Gligor about 10 years ago

OK. In this case I have to do some changes on the ClientCore and potentially on other places.
I think that using the temporary accounts implementation is OK to authenticate for server key import and user redirection.
I have to use a copy of the original bootstrap configuration file for this purpose.
On CTRL-C in this case we don't need a temporary account credentials, I think that the Majic login plug-in should occur on the client web page.
Is this correct? If yes we have to revert also the changes form StandardServer.

#300 Updated by Constantin Asofiei about 10 years ago

Marius Gligor wrote:

I think that using the temporary accounts implementation is OK to authenticate for server key import and user redirection.

Correct, this is their purpose.

I have to use a copy of the original bootstrap configuration file for this purpose.

Correct.

On CTRL-C in this case we don't need a temporary account credentials, I think that the Majic login plug-in should occur on the client web page.

Correct, the custom login plug-in is invoked and must be shown on the client web page.

Is this correct?

CTRL-C in MAJIC doesn't unwind all the way back to the login screen - is caught and handled in the business logic, to keep the user in the main menu. In normal cases, when CTRL-C is not caught, it will need to act as the client is restarted - so yes, this will unwind all the way to the while (running) loop in ClientCore.start.

If yes we have to revert also the changes form StandardServer.

If the changes are no longer needed, they should be removed.

#301 Updated by Marius Gligor about 10 years ago

OK Constatin thanks. Tomorrow I will try to fix this issue.
Are you did tests with the web client? How it's works?

#302 Updated by Greg Shah about 10 years ago

1. The browser context menu can be controlled from JS on all major web browsers IE, Firefox and Chrome:

Go ahead and implement this.

2. The navigation buttons on the other hand cannot be controlled from JS to be hidden or disabled.

For now, I want to avoid the problems that come with a popup window.

We could put an implementation of 2a (JS approach) in ui/client/chui/driver/web/chui_web.html. This is the previous page in our case, right?

Another approach is to edit/control the browser's history. If the previous entry in the history is the current page, this may be a solution to the back/forward problem. Please see:

https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Manipulating_the_browser_history

I have also to do some tests in order to find out if it's possible to use short-cuts keys in pop-up windows to navigate back.

Please do let me know the results of your tests here.

Currently whenever the user try to navigate back or reload (refresh) the page the web socket connection is closed and the client process is terminated.
The user should go to the authentication page and restart a new session.
A good idea is not to terminate the process when the user refresh the page and close the socket connection but instead we should use the "watchdog" timer.

Hmmm. This is a good find on the refresh issue. I had not thought of that.

Some questions:

1. Does the use of the 2a JS "solution" still cause the back to sever the web socket connection?
2. Can the browser history manipulation help here?
3. Can we get a notification on refresh? If so, can we cancel it?
4. Can we make the websocket survive by using a separate worker thread (and separate window object) as documented in note 184 point number 4?

#303 Updated by Marius Gligor about 10 years ago

I fixed the issue related to server key store import. It was a misunderstanding related to implementation.
For a web client first a temporary session is established using a temporary account.
Inside the main loop of ClientCore.start user credentials are used.
As a result I reverted the changes from MainEntry, StandardServer and TemporaryAccount classes.
Also the Result class is no longer used and has been deleted.
This design gracefully simplify the ClientCore.start code.
Basically the code looks (main loop of ClientCore.start) like before introducing web clients.
Also I added some JS code inside the web pages which prevents the back navigation and disable the browser context menu.
Nevertheless we have to do more works on the JS front-end depending on the final solution we will choose.

#304 Updated by Constantin Asofiei about 10 years ago

Marius, 0131a.zip solves the login problem (I'll let Greg do a full review), but there are some other issues in MAJIC (I'm using firefox 26.0 for ubuntu canonical 1):
  1. in the main MAJIC menu, when moving around, the selected option is "highlighted", via the put screen color message statement. This does not work in web mode.
  2. the cursor is not blinking in MAJIC - don't know if this is a real issue or not.
  3. I don't think we should allow the input cursor position to be changed at mouse click. IIRC, the position of the cursor is changed during chui drawing and it should be out of user reach.
  4. there are artefacts remaining after the screen is cleared (is like some portions of the chars are left behind...); see for the "breadcrumbs".
  5. you should to redirect the standard error and output streams for the spawned process to a file somewhere - else, it will be difficult to debug through configuration or other kind of problems, on the client side.
  6. not sure why, but process launching does not work while in web mode. Use a code like this to test (a.txt is a file with a single line of text):
    def var ch as char.
    input thru value("cat a.txt") no-echo.
    import unformatted ch.
    input close.
    message ch.
    
  7. in firefox, when redirecting to the custom url, I always get a This Connection is Untrusted screen first (to accept the certificate).

#305 Updated by Marius Gligor about 10 years ago

1. Put screen color message statement isn't implemented on the web client. I have to check how to do that. Could you please attach a picture for this issue?
2. The cursor could be "solid" or "line" blinking and not blinking, visible or hidden according to the cursor settings (see the JSON used for initialization inside index.html).
3. Greg told me to keep the mouse events but it is not a problem to cut off.
4. Rendering is another issue that is browser and platform dependent. In this case we have to do some small changes in the drawing code to fix the problem.
5. I think that we can use the client log file. Maybe a good idea is to spawn the client and activate the log at this time.
6. I have no idea about this issue, maybe you can offer me more details like where is the file located, how is loaded etc.
I'm working only for 3 moths on this complex system and so far I have no enough knowledges about all aspects regarding the system capabilities.
7. This message cannot be eliminated as long as we use self signed certificates.

#306 Updated by Marius Gligor about 10 years ago

A better way to prevent back navigation by editing history.

#307 Updated by Constantin Asofiei about 10 years ago

Marius Gligor wrote:

1. Put screen color message statement isn't implemented on the web client. I have to check how to do that. Could you please attach a picture for this issue?

See the attached .
You can convert the menu.p program from the testcases project and run it with the ask.p.

2. The cursor could be "solid" or "line" blinking and not blinking, visible or hidden according to the cursor settings (see the JSON used for initialization inside index.html).

You can run menu.p (via the pro -p menu.p command) on lindev01 and see how it works. In 4GL, the cursor is solid and not blinking.

3. Greg told me to keep the mouse events but it is not a problem to cut off.

OK, I understand now. I was worried that the drawing code will read the cursor position after/during drawing, and it will read this modified position (thus breaking drawing). But I don't think this is the case.

4. Rendering is another issue that is browser and platform dependent. In this case we have to do some small changes in the drawing code to fix the problem.

OK

5. I think that we can use the client log file. Maybe a good idea is to spawn the client and activate the log at this time.

Maybe change spawn.c to accept a file name where to redirect the stdout and stderr? Otherwise, another option is to redirect them via ProcessBuilder.redirectError and redirectOutput.

6. I have no idea about this issue, maybe you can offer me more details like where is the file located, how is loaded etc.

Run the io/process_streamt_test.p program via ask.p - this will do a basic process test. This test will create a file named some_output_file.txt with this content:

txt
--------
This is

7. This message cannot be eliminated as long as we use self signed certificates.

OK, I understand.

Greg, some notes:
- the untrusted connection issue will be annoying for "live" usage; for testing is OK, but having to go through this screen each time during login in a production environment, I don't think is OK.
- about the web socket timeout: IIRC, in MAJIC there are some end of month reports which take a lot of time (much more than 15-30 minutes). The timeout is OK in a "away from keyboard" scenario, but it will be problematic in cases of long-running server-side tasks or reports.

#308 Updated by Marius Gligor about 10 years ago

1. The redirection to a log file is not possible from ProcessBuilder because we redirect the stderr for spawner not for web client process.
Form spawner is also difficult and we have to found solutions for Linux and Windows.
However I found a better way which works like magic!
- I did the redirection from ClientDriver.main function the entry point of the client process. If the process is a web client
(if found the following string client:driver:type=chui_web in args list) than I constructed a file name like client_$user_$time.log
and I redirected the stderr to this file. The log file is created into client working directory.

2. The socket timeout is configurable. Nevertheless we have to improve this part (see watchdog proposals)

#309 Updated by Marius Gligor about 10 years ago

I added the ClientDriver log file redirection implementation.

#310 Updated by Marius Gligor about 10 years ago

1. The user Refresh action could not be detected. Is the same like closing the browser.
When the user reload the page the web socket is closed and the client process exit.
In order to avoid this behaviour I designed a watchdog timer thread.

When the process (chui simulator) is started also the watchdog timer thread is started.
When a connection is made over web sockets the watchdog is killed.
When the web socket connection is closed the watchdog timer thread is restarted.
In respect with this the whatchdog looks like an idle task.
The whatchdog timer thread is responsible to terminate the process when timeout expire.
Currently I'm working to implement the whatchdog timer.
Because now the whatchdog timer is responsible with process termination I think that the timeout stored
on registry could be used by whatchdog and the socket timeout is no longer a critical issue.
With the whatchdog in place the user could connect and reconnect to server at any moment.
Now the Refresh (reloading the page) will work properly.

2. On a short TODO list I have to fix also:

- send also text colour to web chui simulator.
- fix the drawing issue reported by Constantin.
- other issues reported during tests.

#311 Updated by Constantin Asofiei about 10 years ago

Marius, about 0131c.zip:
  • please make an update without the JS changes in the .html files, and get it conversion test it. I need this update, as others can start these kind of clients using the spawner: appserver agents and batch processes. Also, I'm refactoring some of the spawner-related infrastructure, to make it unaware of who starts the P2J client.
  • you removed the synchronization from TemporaryAccount - this is still needed.

#312 Updated by Marius Gligor about 10 years ago

Here are the package that I used today on the regression tests.
Passed the regression tests.

#313 Updated by Constantin Asofiei about 10 years ago

The changes look good to me. If Greg has no objections, you can release them.

#314 Updated by Greg Shah about 10 years ago

In regard to 0203a, I have some minor concerns with the ClientDriver changes, but please go ahead and commit/distribute the update anyway.

In your next update, address the following in ClientDriver:

1. Setting the driver can be done from both the bootstrap config file as well as from the command line. I realize that we currently drive this from the spawner and it will (for now) always come from the command line. But this creates a hidden dependency that may "bite us" later. I prefer to process off the bootstrap config after the command line overrides are merged in. Hopefully that is not too late for the redirection to be useful.

2. We should provide a mechanism to override the log file name (and optionally the pathing too) rather than only allow a fixed log name. Perhaps we should calculate the logfile on the server and set it into the command line args/bootstrap config. That would allow us to read overrides from the directory if present.

#315 Updated by Marius Gligor about 10 years ago

During the weekend I tried to do some tests on Windows OS and I found two important issues:

1. The File.renameTo(File target) does not work properly in Windows. If target file already exists the method fails.
Tried to rename the directory.xml.tmp to directory.xml fails because directory.xml already exists.
Seems to be a known issue since I found a TODO remarks inside the class.
I fixed this issue inside the class XmlRemapper using the JDK 7 Files.move call.
Now it's works fine on both Linux and Windows OS.

2. I cannot build the native artefacts due to the new libffi dependencies. I tried to fix this issue but unfortunately with no success.
I cloned the libffi project from git hub repository and I tried to build the libffi.
The build seems to be OK but I cannot use the resulted lib file for linking in Windows OS.
I'm not sure if building under Linux for x86 could be used also in Windows.
I didn't spent much time with this issue.
Is anybody on the team that could help us with this issue?
The problem is for Windows OS only.

Today I started to fix the terminal colour issue. Seems to be not a colour issue but a font colour attributes issue.
So far I did some changes inside the code to implements text colour attributes and the results are good (see the attached picture).
I adjusted also some rendering parameters in order to fix the drawing issue. I have to do some tests with my new changes.
Unfortunately another issue occur, this time at spawner level.
Bellow are Constantin remarks:

The problem is because a process can not be launched (it needs to run "nice mail -e 2> /dev/null ; echo $?" using 4GL's INPUT THROUGH statement).
You can ignore this error while adding support for the highlight feature.
But please investigate why, for a P2J client started using the spawner, processes can not be started.
See the pseudoTerminalLaunch function from src/naive/process_linux.c. Process launching fails at this test:

if (!isatty(STDIN_FILENO))
{
return -2;
}

I'll try to investigate also this new issue.

#316 Updated by Greg Shah about 10 years ago

In regard to the libffi issue, please see note 74 in #1634.

#317 Updated by Greg Shah about 10 years ago

Code Review 0203b

I am fine with this change.

#318 Updated by Greg Shah about 10 years ago

- the untrusted connection issue will be annoying for "live" usage; for testing is OK, but having to go through this screen each time during login in a production environment, I don't think is OK.

Self-signed certificates must be allowed and would be the default. But any customer can do the following to eliminate the problem:

1. Implement a certificate that is signed by a root authority that is already recognized by the browsers in use. This is commonly done for HTTPS servers and it will work for our server too. It is just a matter of properly importing the cert into our directory.

2. Add the root authority used for self-signing to the browser's configuration. This is possible if all the browsers are deployed under the control of that customer.

Anyway, do you see something else we need to implement to make this better? As far as I know, there is nothing else to do from our perspective. Our current approach just reads the server's keystore over a RemoteObject connection and re-uses that on the embedded web server in the client. Basically, any certificate configured to secure the P2J server will be used automatically by all the web clients.

#319 Updated by Marius Gligor about 10 years ago

Here is the implementation of text attributes (color)
I found the following attributes defined (see ColorAttribute).

NORMAL(0x00), 
BLINK(0x01),
BOLD(0x02),
UNDERLINE(0x04),
REVERSE(0x08);

I implemented all this attributes except BLINK.
Theoretical is possible to implement the BLINK in JS but I'm afraid the performances will be affected and the feature not usable.
For performances reasons I used a binary protocol to send attributes down to client.

I did also some small changes on the drawing code.
Unfortunately this part depends on browser and platform on which the JS code runs.
According to my tests it works fine on Firefox/Linux having a font size between 16 and 20.

I designed and implemented also a Watchdog Timer to guard the web client life.
I moved to JS code used to avoid back navigation and access the browser menu context inside tty.js file.

#320 Updated by Greg Shah about 10 years ago

Code Review 0204a

Overall, this is very good.

1. In ChuiWebSimulator, please synchonize any access or assignment to the watchdog data member since there is no guarantee that it won't be set and accessed from different threads.

2. In the WatchdogTimer.run(), please check the running flag after exiting the sleep() since it is possible that the user could have connected and a kill() was issued, dropping the thread out of the sleep early. But if the timer had elapsed, the system will exit, even though the user may think they have come back in. In other words, let the user connection take precedence over the timer.

3. As Constantin noted above, it is a common case for these clients to be left running for a very long time (e.g. during lunch). We may need to provide an option to completely disable the watchdog timer. Constantin, what do you think?

4. Our NCURSES implementation has recently be updated to support real colors (see ColorPalette, Color and ConsolePrimitives classes). Our Swing and Web ChUI clients need to be similarly enhanced.

#321 Updated by Constantin Asofiei about 10 years ago

Greg Shah wrote:

3. As Constantin noted above, it is a common case for these clients to be left running for a very long time (e.g. during lunch). We may need to provide an option to completely disable the watchdog timer. Constantin, what do you think?

If I understand correctly, this watchdog timer and web socket timeout is needed to be able to close the P2J client when the user closes the browser, right? If this is the case, why don't we add a "heartbeat" in JS code? As long as this heartbeat is received by the web server, we can assume that the user is still using the application (and the watchdog can can listen only for this heartbeat).

#322 Updated by Marius Gligor about 10 years ago

Indeed. The watchdog timer has been designed to terminate the process when the user close the browser.
Even more when the client use the reload button to reload the page the web socket is closed and than reopen after page is reloaded.
During the page reload the application should remain active. The watchdog guardian help us to do that istead to terminate the application like on the previous implementation.
If the page is not reloaded due to an unexpected reason the guardian is active and the process will finally terminate after an amount of time.

#323 Updated by Marius Gligor about 10 years ago

Here are the latest changes. I fixed also the issue related to isatty() when using spawner.
It was a bug on my spawner code. Now it works properly.

#324 Updated by Greg Shah about 10 years ago

Code Review 0205a

1. The WAIT_TO_DIE javadoc is inaccurate.

2. Please rename the sartWatchdog() to startWatchdog().

3. What happens when someone executes something in the converted app that causes the app to exit (e.g. execute a QUIT statement)? What does the user see in this case? I guess it should probably redirect back to the original login web page.

#325 Updated by Marius Gligor about 10 years ago

  • File mag_upd20140206a.zip added

1. The WAIT_TO_DIE javadoc is inaccurate.
Done.

2. Please rename the sartWatchdog() to startWatchdog().
Done.

3. What happens when someone executes something in the converted app that causes the app to exit (e.g. execute a QUIT statement)?
What does the user see in this case? I guess it should probably redirect back to the original login web page.

Good questions? I designed and implemented a flow to redirect the user to the login page in case of converted application exit.
a) On the server side:
I added a new command line parameter web:referrer:url which store the referrer http header value, that is,
the URL address at which the client should be redirected. This command line argument is used by the client builder when a new client is spawn.
b) On the client side:
On the main loop of ClientCore.start when the converted application exit (running = false) for web clients I added a method quit() on screen driver
which further will notify the JS code running on the web browser via ChuiWebSimulator.quit and a new web sockets command.
This command provide the referrer address to the remote JS code. When the command arrive to the JS code in browser the client is redirected to the referrer address.
As a protection I added also a Cache-Control field into HTTP headers when the page containing the JS code is loaded (index.html).
This instruct the browser to not cache or store the page.
When the user try to go back to this page after the redirection is done the browser will inform the client about the missing page.
The client must follow the normal way and do another authentication in order to start a new session.

#326 Updated by Greg Shah about 10 years ago

Were the changes to tty.screen.js and tty.socket.js deliberately removed from 0206a?

#327 Updated by Marius Gligor about 10 years ago

  • File deleted (mag_upd20140206a.zip)

#328 Updated by Marius Gligor about 10 years ago

Sorry, it was my mistake. I have a short script to build the archive and I had a small problem inside the script.
Please take the new archive.

#329 Updated by Greg Shah about 10 years ago

Code Review 0206a

It looks really good. Other than adding real color support, is there anything else left to do?

The real color support can be done later.

#330 Updated by Marius Gligor about 10 years ago

From my point of view I think no.

#331 Updated by Greg Shah about 10 years ago

OK. Get this regression tested.

#332 Updated by Greg Shah about 10 years ago

Hynek recently added proper color support for our NCURSES/WIN32 Console based ChUI client (the ConsoleDriver which is backed by the libp2j.so/p2j.dll).

Please extend this same level of support to both the Swing ChUI and AJAX ChUI clients.

#333 Updated by Constantin Asofiei about 10 years ago

Marius, a note about 0129a.zip: the archive has a tty.keymap.js file which is not in bzr rev 10457. Please check 0129a.zip against 10457 revision, to be sure nothing was lost. You can include any missing files with the 0206a.zip update.

#334 Updated by Marius Gligor about 10 years ago

I sow now that is missing. It wasn't add to bzr. I will include this file also on the next commit.
Thanks.

#335 Updated by Marius Gligor about 10 years ago

  • File regression_tests_reports_01.zip added
  • File regression_tests_reports_02.zip added

Here are the results of my regression tests.
For the first time the CTRL-C test passed.
The main test part has only one failure but is a negative one.

#336 Updated by Greg Shah about 10 years ago

The tc_job_002 is expected to fail at that point, so this testing passes.

Please commit the update and distribute it.

#337 Updated by Marius Gligor about 10 years ago

May I delete the attached files containing the test results?

#338 Updated by Greg Shah about 10 years ago

Yes. You just need to post your assessment of the regression testing into Redmine, not the results themselves. At this point, I think you have enough experience with the testing process to assess things yourself.

#339 Updated by Marius Gligor about 10 years ago

  • File deleted (regression_tests_reports_01.zip)

#340 Updated by Marius Gligor about 10 years ago

  • File deleted (regression_tests_reports_02.zip)

#341 Updated by Marius Gligor about 10 years ago

Update [mag_upd20140206a.zip]
Passed regression tests, committed to bzr revision 10459.

#342 Updated by Marius Gligor about 10 years ago

Today I started to work on issue #2240 regarding color implementation on swing and web chui clients.
I studied the implementation for NCURSES and I reread also the p4g manual regarding colors in character terminals.
What I understood so far is that the color field in Color class holds a pair value foreground/background color.
The integer values for FG and BG colors are indexes into a color palette structure.
Currently the ColorPalette has only 8 entries. The p4g states the we could have max 127 colors (entries).

1. It is possible in the future to have more entries inside ColorPalette?
It is important to know in order to optimize the transfer of colors wia web sockets.
Up to 16 entries we could use 4 bits/color and 1 byte for FG/BG pairs.
If we have more than 16 entries we should use 1 byte/color and 2 bytes for FG/BG pairs.

2. The files color-map.dtd and color-map.xml are the substitute of protermcap file?

3. Where is the color-map.xml? (see the attached client log)
Without this file the native client seems to not work with colors.

4. The color-map.dtd define also 8 colors for FG and BG.
The same question regarding the number of colors that could be defined.

#343 Updated by Greg Shah about 10 years ago

First, I should have previously mentioned that details about the most recent color work can be found in #1801.

1. It is possible in the future to have more entries inside ColorPalette?

This enum does define the well-known terminal colors. So we won't be directly adding colors in the ColorPalette enum, but custom colors will be possible in the terminal.

2. The files color-map.dtd and color-map.xml are the substitute of protermcap file?

The color-map.xml is an optional file that is read from the converted application's jar file and which defines custom colors and color name/index mappings that can be referenced from the converted code.

The DTD is just to define/validate the proper structure of the color-map.xml.

3. Where is the color-map.xml? (see the attached client log)

See #1801 for an example. This is application specific, so by default there won't be one in P2J.

The same question regarding the number of colors that could be defined.

We have to assume that we must provide support for the maximum number of colors that are supported by the 4GL.

But the details of a given color definition are different from the color index that will be passed. In particular, we can split the implementation in 2 parts:

1. Custom color definitions get registered in the driver. This effectively would tell the driver how to render a new color AND what color index is associated with it. This would be done one time, when the client is initialized.

2. For a given character on the screen we just need to know the index data of the color being used. This is the data actually sent at drawing time for each character. This is the data to optimize.

Please review #1801.

#344 Updated by Constantin Asofiei about 10 years ago

Marius, I'm merging your 0206a.zip update with mine and mainPage and indexPage handlers in ChuiWebDriver.startupServer don't look right: shouldn't mainPage target /, instead of /index.html? Now they look like they target both the same page...

#345 Updated by Hynek Cihlar about 10 years ago

Hynek recently added proper color support for our NCURSES/WIN32 Console based ChUI client (the ConsoleDriver which is backed by the libp2j.so/p2j.dll).

Note that color support in WIN32 part of the native terminal driver is not implemented yet.

#346 Updated by Hynek Cihlar about 10 years ago

I am not sure how the custom colors will be defined for the Swing and Web chuis, but at the moment color-map.xml may be too much terminal-oriented. For example, for bright-red color in xterm terminal, you have to combine the (simple) Red color and the Blink attribute.

#347 Updated by Marius Gligor about 10 years ago

We have 2 jetty handlers for two targets ./ and ./index.html which load the same page:

1. When the user is first redirected at https://server:port/ the target is /. which load the page index.html
On browser address bar the URL is https://server:port/index.html after redirection.

2. Once redirected if the user do a reload (refresh) of page having the address https://server:port/index.html
the target is now /index.html which in turn will load the same page index.html

#348 Updated by Greg Shah about 10 years ago

Please add some javadoc to describe these important points.

#349 Updated by Marius Gligor about 10 years ago

Today I did a lot of investigations related to COLOR implementation on CHUI Swing and WEB clients.
First I attached some important paragraphs extracted from the P4G manuals (see color.txt).
According to the P4G manuals:

1. COLOR phrase is mentained for compatibility with the old versions.
The COLOR phrase was superseded by DCOLOR and PFCOLOR on character interfaces and FGCOLOR and BGCOLOR for graphical interfaces.
Actually in P2J only DCOLOR, PFCOLOR and partial old COLOR (NORMAL | INPUT | MESSAGES, protermcap-attribute (color paires like BLACK/WHITE), dos-hex-attribute are supported.

2. According to the P4G manuals the DEFAULT_COLOR_TABLE has 16 colors (0...15) which are reserved, that is, SHOULD not be changed.
However the user could define custom colors starting with index 16. No limits are specified but 256 entries in the table might be a good limit.

3. In character interface FGCOLOR and BGCOLOR are undefined. The DCOLOR and PFCOLOR are indexes in a COLOR PAIR table.
The entries in COLOR_PAIR table holds foreground/background color pairs.
The colors MUST be defined, that is, should be defined inside the COLOR TABLE (DEFAULT_COLOR_TABLE + custom colors).
The first 5 (0 to 4) entries in the COLOR_PAIR table are reserved. The user could define custom pairs values starting with index 5 to 127.
In total the COLOR_PAIR table could holds maximum 127 entries.

4. The actual implementation for chui native driver introduces some limitations.
- It define only 8 default colors.
- Custom colors aren't supported.

5. Some Java code is common for all terminal implementations and I think that we have to find a solution
to have the same code on all drivers. For example the ColorSpec and Color classes.

In conclusion, in my opinion before to implements the screen rendering we have to design and implements the P4G color mapping parts according to P4G specifications.
Than we could easily convert the P4G color indexes into rendering colors (Swing and Web colors) and implements also the rendering part.
In order to do that we have to decide where to store the CUSTOM COLORS defined by user and the PROTERMCAP file to holds the custom COLOR_PAIR custom entries.
Could we leverage the existing color_map.xml file to be used for our purposes?

#350 Updated by Marius Gligor about 10 years ago

Based on the current COLOR implementation for ChUI native driver I implemented the COLOR for ChUI Swing driver too. (see attached picture)

1. First I converted the chui_colors.p from testcases/uast.
Because I got an error when chui_colors.p was converted I changed the first line:
define var ed as character view-as editor size 20 by 2 label "Editor" pfcolor 3.
to:
define var ed as character view-as editor size 20 by 2 label "Editor".

Seems that pfcolor 3 cannot be converted or not supported.

2. I used the color-map.xml file posted by Hynek and I implemented the COLOR for Swing driver.
First I defined colors for terminal type "vt100" because the vt100 is defined as monochrome (BLACK/WHITE) inside color-map.xml.
By the way from where it is possible to set the terminal type?
Also I improved the Swing drawing using the Double Buffered option for JComponent.
The current changed are attached together with some picture snapshots.

3. Now I'm working to implement the COLOR for ChUI Web driver.

#351 Updated by Hynek Cihlar about 10 years ago

Seems that pfcolor 3 cannot be converted or not supported.

Looks like PFCOLOR is parsed ok and a valid AST is built. Probably a bug during emitting the Java code. I'll create an issue for this. Also I committed a fixed chui_colors.p so it compiles fine.

By the way from where it is possible to set the terminal type?

Terminal type is retrieved from the actual screen driver (see ScreenDriver.terminalType). For the linux driver for example, the terminal type is ultimately fetched from the TERM env variable.

Progress also contains the TERMINAL statement for changing the terminal type. However I am not sure whether this is currently supported and I didn't scope this in when implementing #2037.

#352 Updated by Greg Shah about 10 years ago

2. According to the P4G manuals the DEFAULT_COLOR_TABLE has 16 colors (0...15) which are reserved, that is, SHOULD not be changed. However the user could define custom colors starting with index 16. No limits are specified but 256 entries in the table might be a good limit.

We have to assume that some customers will actually modify these first 16 entries, so don't exclude that case.

in my opinion before to implements the screen rendering we have to design and implements the P4G color mapping parts according to P4G specifications.

Agreed. I prefer to maximize common code and only have differences at the driver level (if possible).

In order to do that we have to decide where to store the CUSTOM COLORS defined by user and the PROTERMCAP file to holds the custom COLOR_PAIR custom entries.
Could we leverage the existing color_map.xml file to be used for our purposes?

The color-map.xml is intended to be the P2J replacement for the PROTERMCAP. It will also store the custom colors defined on Windows (which the 4GL stores in the registry).

We will have a single, simplified approach and only use this color-map.xml file.

#353 Updated by Greg Shah about 10 years ago

Code Review 0211a

1. In ColorPalette, let's use RGB values instead of the AWT Color instances. This is more universal and can be easily translated to both Swing and Web clients.

2. In the ChuiSimulator, I would like to avoid the color/font processing and drawing for each character. Please consider ways to batch up use of the same font (including the same foreground/background color) for multiple characters.

#354 Updated by Marius Gligor about 10 years ago

1. AWT Color instances are build using now RGB values in ColorPalette.

2. The code used to set the Font color attributes was moved to the right place ColorMapper class.
The ChuiSimulator code has been reverted to it original optimized code. I added only the double buffered option.

3. Implemented COLOR in ChUI Web client. The color palette is send down to JS client only when web sockets connection is open.
Apart the changes to implements COLOR I did also a lot of major changes inside JS code (tty.screen.js) in order to manage the screen cursor.
In the current implementation we have only 8 colors defined so for performances reasons the color pairs FG/BG are assembled and stored
in only 1 byte, MSB 4 bits for FG and LSB 4 bits for BG color numbers. This encode scheme allow mapping of up to 16 values in the color palette.
For character interfaces a limit of 16 colors is a reasonable value.
If you consider to have more colors let say 1 byte for FG and another byte for BG which allows to map up to 256 colors please let me know.

#355 Updated by Greg Shah about 10 years ago

Code Review 0212a

1. Please note that our maximum line length is 98 characters. The docs haven't been updated. You don't need to break down the javadoc lines to fit within 78 chars.

2. Please rename ChuiWebSimulator.builPaletteMessage() to ChuiWebSimulator.buildPaletteMessage().

3. ChuiWebSimulator.clear(), ChuiWebSimulator.setCursor() and ChuiWebSimulator.setCursorStatus() are missing javadoc.

4. You mention that the color palette is sent to the client at websocket open. But actually, it is sent down every time we refresh the screen (i.e. sync()). That seems too often. I don't think Progress has a feature to dynamically modify the color palette. If I'm correct, it only needs to be sent once.

so for performances reasons the color pairs FG/BG are assembled and stored
in only 1 byte, MSB 4 bits for FG and LSB 4 bits for BG color numbers. This encode scheme allow mapping of up to 16 values in the color palette.
For character interfaces a limit of 16 colors is a reasonable value.

This is fine for now. We can change it later if requirements present themselves.

#356 Updated by Marius Gligor about 10 years ago

1. Regarding sync() method. This is a private method called from the onConnect(session) method, when a new session is open.
The onConnect() is called when the main web page is loaded by web browsers.
This could happened on two cases:

- When the user is first redirected to the main page URL. In this case is MANDATORY to call sync()
in order the synchronize the virtual tty with JS tty implementation.
- A second scenario is when the user force the page to be reloaded by pressing the browser Refresh button.
Unfortunately we cannot disable this button from JS. The action of this button is equivalent with the first scenario and the sync() call is also MANDATORY.
Whenever the main page is reloaded as I described the JS code embedded on the page is reinitialized.
That's why we MUST send down color palette because after the page is reloaded the value for this JS variable became "undefined".
Basically the color palette is loaded only on this two cases. During the normal operation no color palette is send down to JS.

2. Today I designed and implemented a cached mechanism for color and attributes messges.
The last send color or attribute messages are cached and always compared with the current message.
If no changes are found on the current message the message is not send.
This is a significant improvement of performances especial on monochrome applications where the
color message is basically unchanged during the execution. The color message is send only once
except the case of a refresh like I described on the previous paragraph.

3. I added log messages on WatchdogTimer and I did some tests. According to my tests the watchdog
timer works properly (see attached log file). I tested on two cases:
- when the browser is closed.
- when the tab page of the browser is closed but the browser remains active.

#357 Updated by Greg Shah about 10 years ago

Code Review 0213a

I am fine with the code, except for 1 change. I should have looked more carefully at the sync() method. The problem is that we have a well known sync() method that is part of the driver (actually, the OutputPrimitives instance contained in the driver). That method has a very specific meaning/purpose and when I read the name sync(), I made an assumption that it was the same method. Please change the name of the method to something more descriptive of its purpose so that in the future people won't make the same silly mistake I made.

Unless you have any other remaining changes on this, you can go ahead with regression testing the updated version.

#358 Updated by Marius Gligor about 10 years ago

OK. I have an idea to cache also the text messages. I'm going to implements this feature.

#359 Updated by Marius Gligor about 10 years ago

Renamed sync() method to initRemoteTerminal()
I implemented also the screen characters message caching.

#360 Updated by Greg Shah about 10 years ago

Code Review 0213b

The changes look good. Please get it runtime regression tested.

#361 Updated by Constantin Asofiei about 10 years ago

Marius, I've managed to get a NPE in TemporaryAccountPoolWorker:

Exception in thread "Thread-46" java.lang.NullPointerException
        at com.goldencode.p2j.main.TemporaryAccountWorker.run(TemporaryAccountWorker.java:119)
        at com.goldencode.p2j.security.AssociatedThread.run(AssociatedThread.java:127)

Can't duplicate yet... this was on devsrv01, not in debug mode.

#362 Updated by Marius Gligor about 10 years ago

  • File netbeans-7.4.desktop added

It's a bug. In TemporaryAccountWorker line 119 I omitted to check the callerLatch for null value.
The correct code should looks like:

if (callerLatch != null)
{
   callerLatch.countDown();
}

This could happen when the TemporaryAccountWorker thread is terminated and the execute() method was never call.
The callerLatch remains at initial value which is null.
Looking inside the code this happen when the server is start-up and later shut-down without using any temporary account from pool, no process was spawned during the server session.
Please fix this issue.

#363 Updated by Marius Gligor about 10 years ago

TemporaryAccountWorker thread is used to enable/disable temporary accounts.
Since this operation require a specific security context I used an AssociatedThread instance created earlier.
The access to TemporaryAccountWorker is serialized because we have a single instance.
TemporaryAccountWorker like AssociatedThread accept a Runnable object as task to be executed.
I used two CountDownLatch instances to synchronize between execute() and run() methods.

#364 Updated by Marius Gligor about 10 years ago

I just manage to reproduce the bug. I started than I stopped the server without any other action and the bug occurs.

[02/14/2014 13:40:43 EET] (SecurityManager:FINER) {00000000:00000001:standard} Search done: handle 65, decision true, cache false
Exception in thread "Thread-6" java.lang.NullPointerException
    at com.goldencode.p2j.main.TemporaryAccountWorker.run(TemporaryAccountWorker.java:119)
    at com.goldencode.p2j.security.AssociatedThread.run(AssociatedThread.java:127)

#365 Updated by Constantin Asofiei about 10 years ago

Marius Gligor wrote:

I just manage to reproduce the bug. I started than I stopped the server without any other action and the bug occurs.

[...]

OK, I'll include the fix with my #2124 update.

#366 Updated by Marius Gligor about 10 years ago

Unfortunately I found another NPE bug which occur if the spawn or spawn.exe is missing.
In this case the ProcessBuilder throw an exception at line 101 and the shell variable remains null.

[02/14/2014 13:52:55 EET] (WebHandler.spawn():SEVERE) {qtp1523495872-25} Cannot spawn the process.
java.io.IOException: Cannot run program "/home/mag/p2j/build/lib/spawn": error=2, No such file or directory
        at java.lang.ProcessBuilder.start(ProcessBuilder.java:1041)

Later at line 125 a NPE is throw.

02/14/2014 13:52:55 EET] (org.eclipse.jetty.server.HttpChannel:WARNING) /chui
java.lang.NullPointerException
        at com.goldencode.p2j.main.WebClientSpawner.spawn(WebClientSpawner.java:125)

The bug must be fixed in line 123 as follow:

   if (remoteUri == null && shell != null)
   {
      shell.destroy();
   }

#367 Updated by Constantin Asofiei about 10 years ago

Marius Gligor wrote:

The bug must be fixed in line 123 as follow:

OK, I'll include the fix with my #2124 update, too.

#368 Updated by Marius Gligor about 10 years ago

I saw that you already did some changes inside WebClientSpawner and the code was moved to ClientSpawner.
However the bug remains an could occur in ClientSpawner line 154.

#369 Updated by Constantin Asofiei about 10 years ago

Marius Gligor wrote:

I saw that you already did some changes inside WebClientSpawner and the code was moved to ClientSpawner.
However the bug remains an could occur in ClientSpawner line 154.

Yes, don't worry, the fixes will be in the proper files.

#370 Updated by Constantin Asofiei about 10 years ago

I found another problem in ClientCore this time. There are cases when the P2J Client terminates via System.exit. Thus, the browser will not be redirected back to the login page.
I've tried adding this code before the loop in ClientCore.start (the correct place now would be in WebClientSpawner$TemporaryClient.doWork):

      if (driver instanceof ChuiWebDriver)
      {
         final BootstrapConfig cfg = config;
         final ChuiWebDriver webDriver = (ChuiWebDriver) driver;

         Runtime.getRuntime().addShutdownHook(new Thread()
         {
            @Override
            public void run()
            {
               // remote application is terminated.
               // notify the web client by sending a referrer address.
               String url = cfg.getString("web", "referrer", "url", null);      
               webDriver.quit(url);
            }
         });
      }

but this ends up in a deadlock on SecurityManager singleton, in the catch block of the ChuiWebSimulator.quit method, which catches this exception:
java.io.IOException: Connection output is closed
   at org.eclipse.jetty.websocket.common.io.IOState.assertOutputOpen(IOState.java:101)
   at org.eclipse.jetty.websocket.common.WebSocketRemoteEndpoint.sendFrame(WebSocketRemoteEndpoint.java:167)
   at org.eclipse.jetty.websocket.common.WebSocketRemoteEndpoint.blockingWrite(WebSocketRemoteEndpoint.java:88)
   at org.eclipse.jetty.websocket.common.WebSocketRemoteEndpoint.sendString(WebSocketRemoteEndpoint.java:331)
   at com.goldencode.p2j.ui.client.chui.driver.web.ChuiWebSimulator.sendTextMessage(ChuiWebSimulator.java:266)
   at com.goldencode.p2j.ui.client.chui.driver.web.ChuiWebSimulator.quit(ChuiWebSimulator.java:466)
   at com.goldencode.p2j.ui.client.chui.driver.web.ChuiWebDriver.quit(ChuiWebDriver.java:223)
   at com.goldencode.p2j.main.ClientCore$1.run(ClientCore.java:147)

This tells me we socket is already closed, when this hook is called...

On a side note:
  • the monospaced font is not the same as Monospaced font reported by TerminalOptions.DEF_FONTNAME - I think the names are case-sensitive...
  • Color.getRGB sets bits 24-31 to the alpha value, and are set to FF. For now, readTerminalOptions discards bits 24-31, so only bits 0-23 will be used.
  • I'm running MAJIC on devsrv01 with my #2124 update, I can access the login page, but when I get redirected to the (static) new location, the browser disconnects me. At first I thought I had conflicting certificates, but that is not the case: the client's log says that Jetty is getting started, but for some reason, I can't access the port.

#371 Updated by Marius Gligor about 10 years ago

1. Regarding fonts: During development I realized that fonts are rendered slightly different on web browsers and Java Swing.
The "monospaced" or "Monospaced" is the same but results in an ugly rendering on web client.
I fixed this issue by replacing the "monospaced" with "monospace" in tty.screen.js line 377 because using this font works fine.
"monospace" is not recognized as a monospaced font by SwingHelper.isMonospaced that's why has been fixed on JS code.

I tested also other fixed fonts (http://www.webupd8.org/2010/07/7-of-best-ubuntu-terminal-fixed-width.html)
but the test SwingHelper.isMonospaced fails at the following test:

if (!font.canDisplay('\u2500'))
return false;

which means that this font cannot be used to draw graphical characters (borders). I tested and is true in Swing does not draw borders.
Surprisingly on web browser it works fine! Also as I know that if the browser does not support a specific font he will use another.
Maybe a good idea is to use another parameter to specify the font family for web clients.
The web client does not require fixed fonts because the screen (canvas) is organized as a grid and characters are draw on the canvas.
All that we need is to find the largest width character of the font.

2. Regarding the RGB colors the alpha blending byte is used to define transparency level. A value of 0xFF means opaque.
I thinks that on ChUI interfaces the alpha blending is not used.

#372 Updated by Constantin Asofiei about 10 years ago

Marius Gligor wrote:

I fixed this issue by replacing the "monospaced" with "monospace" in tty.screen.js line 377 because using this font works fine.

Is this replacement done case-insensitive? If not, make it so, because it will allow Monospaced to become monospace in the browser.

2. Regarding the RGB colors the alpha blending byte is used to define transparency level. A value of 0xFF means opaque.
I thinks that on ChUI interfaces the alpha blending is not used.

You might be right, but I can't say for sure. My problem was that the alpha byte was being set to 0xFF which made the web client unusable. That's why I'm dropping the alpha byte (for now) in the last #2124 update.

#373 Updated by Marius Gligor about 10 years ago

Here are the changes which passed today regression tests.

#374 Updated by Marius Gligor about 10 years ago

  • File deleted (netbeans-7.4.desktop)

#375 Updated by Constantin Asofiei about 10 years ago

Marius Gligor wrote:

Here are the changes which passed today regression tests.

You can go ahead and release it.

#376 Updated by Marius Gligor about 10 years ago

I builded a new jetty release. The good news are that web sockets are working now in Google Chrome 32.

#377 Updated by Greg Shah about 10 years ago

I'm OK with this update. Please manually test the following:

1. The admin console on the MAJIC server.
2. Normal (swing client) login to MAJIC and usage of some menus.

I think that if those things are working then there is no need to run standard regression testing since the web server is not in the testing paths.

#378 Updated by Constantin Asofiei about 10 years ago

Marius, there are problems when terminating the P2J server via the "shutdown" command (i.e. -k argument). After at least one web client has accessed the P2J server's jetty server, the normal shutdown doesn't work anymore. There are at least these problems:
  • the TemporaryAccountWorker.run loop ignores InterruptedException's and doesn't terminate.
  • the StandardServer.registerDefaultServices:958 (where the web server is started) must implement the terminate method, so that it will terminate the jetty server, when P2J server shuts down.

I think the same goes for the P2J Client - when the P2J session ends and the queue is stopped (i.e. the server goes down), the embedded jetty needs to be explicitly terminated, so the client can terminate, too.

#379 Updated by Constantin Asofiei about 10 years ago

LE: check if the WatchdogTimer.run:81 while (true) loop needs to terminate if it was interrupted.

#380 Updated by Marius Gligor about 10 years ago

Marius Gligor wrote:

Here are the changes which passed today regression tests.

Passed regression tests. Committed to bzr revision 10464.

#381 Updated by Marius Gligor about 10 years ago

Constantin Asofiei wrote:

LE: check if the WatchdogTimer.run:81 while (true) loop needs to terminate if it was interrupted.

OK I fixed. Inside the loop first check for time elapsed than wait.

#382 Updated by Marius Gligor about 10 years ago

I did an update form the remote repository and I tried a full build "ant all".
The spawn build fails due to:

/usr/bin/ld: cannot find -ljvm

[exec] make -f /home/mag/p2j/src/native/makefile.spawn
[exec] make[1]: Entering directory `/home/mag/p2j/build/native'
[exec] rm -fv ../../build/native/spawn
[exec] gcc -g -Wall -O2 -s -DNDEBUG -I/usr/lib/jvm/java-7-openjdk-amd64/jre/../include -I/usr/lib/jvm/java-7-openjdk-amd64/jre/../include/linux -I/home/mag/p2j/src/native /home/mag/p2j/src/native/spawn.c -o ../../build/native/spawn -lm -lcrypt -ljvm
[exec] /usr/bin/ld: cannot find -ljvm
[exec] collect2: error: ld returned 1 exit status
[exec] make[1]: * [build] Error 1
[exec] make: *
[spawn] Error 2
[exec] make[1]: Leaving directory `/home/mag/p2j/build/native'

#383 Updated by Constantin Asofiei about 10 years ago

Marius Gligor wrote:

I did an update form the remote repository and I tried a full build "ant all".

From my followup email:

Followup: to compile the native code, you need to bring the libjvm.so into the global path. For devsrv01, this was used:

sudo ln -s /usr/lib/jvm/java-7-openjdk-amd64/jre/lib/amd64/server/libjvm.so /usr/lib/libjvm.so

Use "locate libjvm.so" to find the correct location for your system.

This should solve your problem.

#384 Updated by Marius Gligor about 10 years ago

The build of native artefacts now works but the server start-up fails probably due to major changes in design and directory structure.
What can I do in order to start the simple server?
Without the server running I have no options to tests my changes.

#385 Updated by Constantin Asofiei about 10 years ago

Marius Gligor wrote:

The build of native artefacts now works but the server start-up fails probably due to major changes in design and directory structure.
What can I do in order to start the simple server?

See note 38 from #2124 - parts 3, 4, 5.

#386 Updated by Marius Gligor about 10 years ago

I configured the directory.xml and now it works fine.
Just a minor observation.
On note 38 from #2124 - parts 4 for full configuration the "host" attribute should be of type "string" instead "integer".

<node class="integer" name="host">
<node-attribute name="value" value="localhost" />
</node>

correct is:

<node class="string" name="host">
<node-attribute name="value" value="localhost" />
</node>

#387 Updated by Marius Gligor about 10 years ago

Here are the changes that I did today:

1. Redirect web client when client exit via System.exit()
We have two scenario for redirection.

a.) When the remote application is about to end.
b.) When the running client exit via System.exit().
To fix both cases I did the following changes:
- The referrer address is always embedded on the web page inside configuration JSON structure.
a.) When the remote application is about to end the JS client is notified like on the previous
implementation but the referrer in no longer send because is already embedded inside the page.
b.) When the running client exit via System.exit(). The detection of this event on web browser is not possible.
On the web browser page all that we can detect is a web socket close event.
This event could occur in the following cases:
- when the remote server close the connection due to System.exit() call or other reasons.
- when the user press the browser "Refresh" button and the page is reloaded.
In this case a message is show to client in order to inform about the connection closed.
If in an amount of time (5 seconds) the web socket connection is not open or the page is
not loaded the client is redirected to the login page. (see tty.socket.js line 135)

2. I did also a lot of changes in order to fix some bugs or to do improvements.

#388 Updated by Greg Shah about 10 years ago

Code Review 0217a

I am fine with the changes. As far as I can tell, this resolves both the server shutdown issue (note 378) as well as the ClientCore/System.exit() issue. Correct?

Constantin: please review.

Fix anything noted in Constantin's review and upload the final update (if any). Then please get it runtime regression tested.

#389 Updated by Greg Shah about 10 years ago

In regard to the web client performance DOWN frame issue:

I suspect that the issues we are seeing are probably due to the extra trip (from the Java client to the JS client) being added to every DISPLAY. This is done synchronously, right? In other words, a DISPLAY call to the Java client won't return to the P2J server until after the results are flushed down to the JS client, right?

If so, it may be possible to resolve this by making the sync() asynchronous. In other words, sync() in the Java code can push the buffer into the driver and return. Then a separate thread would actually push the screen down to the JS client. This intermediate buffer must be very carefully synchronized such that the pusher thread can only ever see a complete update to the buffer and we never try to push a partial/corrupt change.

Constantin: can you think of any 4GL behavior that this would break?

I will be interested in your findings on the interactive input/editing slowness. I wonder if there isn't a similar solution there too.

#390 Updated by Marius Gligor about 10 years ago

The main difference between web client and swing client is the network layer. Here we have to find solutions to improve performances.
One solution could be the asynchronous transfer mode but we have to investigate also other issues.
The ChuiWebSimulator is based on ChuiSimulator used by swing client and unfortunately is not designed and optimized for web clients.
For example when a keystroke is send to the application the entire screen is sent down to JS client (all cells) in order to update the screen.
I'm sure that here we could implements a better solution to send only one character and the cell location instead the entire screen.

#391 Updated by Constantin Asofiei about 10 years ago

About 0217a: my feeling is that the TemporaryAccountPool and TemporaryAccountWorker changes are prone to deadlocks. You made the TemporaryAccountWorker.execute and terminate synchronized, but what if we are on callerLatch.await(); line 82 on execute and we need to call terminate? As we have a lock on the TemporaryAccountWorker instance when execute was called, that lock was not released by callerLatch.await();, so it is in place; thus, terminate can not be called until the lock on TemporaryAccountWorker instance is released.

Also, combining synchronized blocks with latches is confusing. Have you considered using only synchronized blocks and basic wait/notify, to pick up the task? This would imply posting the task to a queue instead of an instance field. Look at how scheduler.JobProcessor works, although there is a bug I just noticed in there - wait should start only if the queue is empty; the while loop should have been like this:

      while (true)
      {
         synchronized (pendingJobs)
         {
            if (pendingJobs.isEmpty())
            {
               try
               {
                  pendingJobs.wait();
               }
               catch (InterruptedException e)
               {
                  // someone interrupted me, exit
                  break;
               }
            }  

            core = (pendingJobs.isEmpty()) ? null : pendingJobs.removeFirst();
         }

         if (core != null)
         {
            // execute the task.
         }
      }

#392 Updated by Constantin Asofiei about 10 years ago

Ah, and if you need for the caller to wait for the task to be finished, you can always use task.wait() at the caller and task.notify() at the TemporaryAccountWorker. actually, this will not work if the thread executes the task before you call wait, so sending a latch alongside with the task will be required (or do not wait if the task is already terminated...).

#393 Updated by Marius Gligor about 10 years ago

The deadlocks does not occur. The callerLatch is always counted down on the finally block when the current task execution ended. Than the execute method will release the lock (synchronized). In other words either execute() or terminate() has the lock at one moment.
The call to execute() method is always synchronized on TemporaryAccountPool so I eliminated the synchronized from TemporaryAccountWorker and I added
synchronized for call to terminate() also in TemporaryAccountPool.
The old method for synchronization using wait/notify should be designed carefully due to "spurious" wake-ups the should occur especially on Linux OS.

#394 Updated by Marius Gligor about 10 years ago

Here are my latest changes.

#395 Updated by Marius Gligor about 10 years ago

The TemporaryAccountWorker is a single instance as is declared as static. It was designed to execute one single task at a moment.
When no tasks are executed the TemporaryAccountWorker should wait. Obviously the task execution is serialized.
When a task is executed the caller MUST wait until task execution end.

#396 Updated by Greg Shah about 10 years ago

Code Review 0217b

I am fine with the changes.

Constantin: based on these changes and Marius' comments, do you have any other feedback?

#397 Updated by Constantin Asofiei about 10 years ago

Greg Shah wrote:

Constantin: based on these changes and Marius' comments, do you have any other feedback?

The code makes sense now.

Marius: make sure to test that, in MAJIC:
1. ./server.sh -k -i<instance> shuts down the server, in these two cases:
- server just started, login with terminal client, shut down server
- server just started, login with web client, shut down the server - the web client should redirect to login page, too.

2. having an active P2J web client, the P2J server abends (i.e. terminate the P2J server process manually with the kill command) - the P2J web client needs to be terminated and the browser should redirect.

#398 Updated by Marius Gligor about 10 years ago

I tested the redirection by killing P2J web client manually and its works on my workstation.
Nevertheless I will test also with Majic. I already downloaded the Majic from my testing environment on devsrv01.
I have to do the appropriate configurations and than try to improve performances and fix all other issues.
Should I started the regression tests using my latest changes or is better to wait until the Majic tests and improvements will be done?

#399 Updated by Constantin Asofiei about 10 years ago

Marius Gligor wrote:

Should I started the regression tests using my latest changes or is better to wait until the Majic tests and improvements will be done?

You need to include the scenarios I've noted above as part of the regression testing for 0217b (test them using MAJIC). The improvements will be in another update, we need to fix the "-k", as it is broken now.

#400 Updated by Greg Shah about 10 years ago

You are a go for regression testing on 0217b.

#401 Updated by Constantin Asofiei about 10 years ago

Greg Shah wrote:

Constantin: can you think of any 4GL behavior that this would break?

In terms of 4GL behaviour, I don't see any problems at this time. But I wonder if we shouldn't push to the JS side just the portion(s) of the screen being changed... to not redraw the entire screen each time something changed. For a 24x80 terminal the drawing already seems slow, but with a larger terminal or when we will move into GUI, we will experience problems.

Also, about the interactive input: we need to keep in mind that a user key-press is not processed on the JS side; the key first needs to reach the remote P2J Client side (and even the P2J Server side, in case of triggers) and only after that may or may not end up on screen. So, I'm not sure how much we can improve this, as the JS screen will "see" the key only when it picks up the updated screen from P2J Client side. At this time, my feeling is that a major part of the slowdown is caused by the fact that we redraw the entire screen, no matter how small the affected portion is. What you suggest will do some improvement, as the P2J side will not have to wait for the JS side to finish drawing, but the JS side will still be slow on drawing - and it might have a hard time to keep up with all the updated screens, coming from the P2J side.

#402 Updated by Constantin Asofiei about 10 years ago

Marius: another part which is not working in Web (or Swing) mode is the UNIX. statement. This suspends the P2J client and opens a shell. See the shell_commands.p and unix-cmd.p programs; adjust shell_commands.p (or write another test) to add a plain UNIX. statement, and see how this behaves in terminal and web mode. See #2244 for the MAJIC scenario about how to launch the shell.

#403 Updated by Marius Gligor about 10 years ago

1. I started to find a solution to improve the web ChUI client. I have to improve both network traffic and JS rendering part.
Because JS code is very slow I have and idea to send cells data down to JS code in a binary format and per/line not the entire screen.
In other words whenever something is changed on a screen line the line is send down to JS to be updated.
On the JS only the changed positions from the line should be redraw. After this flaw is implemented and tested the next step is to send
data to JS asynchronous using a separate thread to push messages.

2. Regarding UNIX statement, at this moment I have no idea how to solve. We will see after I will finished the improvements.
In my opinion this should be fixed on the P2J client code.
But what happen when the client are using the ChUI web client and he is on another machine?

3. I restarted today the CTRL-C regression tests once again. Why it not passes?

#404 Updated by Constantin Asofiei about 10 years ago

Marius Gligor wrote:

2. Regarding UNIX statement, at this moment I have no idea how to solve. We will see after I will finished the improvements.
In my opinion this should be fixed on the P2J client code.

This will be a tricky one, because UNIX suspends the P2J Client and currently we don't have a way of capturing the native process input/output. We might need changes in the process launching code.

But what happen when the client are using the ChUI web client and he is on another machine?

The shell, other process launching and even file reading/writing is always relative to the P2J Client, not the P2J Server. So if the P2J Client is on another machine, it must open a shell on that machine, not the server's.

3. I restarted today the CTRL-C regression tests once again. Why it not passes?

I know CTRL-C becomes more and more annoying, but we need to deal with it as it is, for now. The failures in your last email look like false-negatives; for some reason, at some point the connection to the remote server can't be established, and I don't know if is related to machine load or some race condition already existing in the P2J persistence code which establishes the remote DB connection (CTRL-C uses some very quick connect/disconnect scenarios, so this might not even be a real issue after all). Greg: at some point we should add some logging in the MAJIC program which establishes the remote connection, and see if the ERROR-STATUS reports any errors; we should make CTRL-C stable, as at some point it might hurt us if some bug hides behind the failures.

#405 Updated by Greg Shah about 10 years ago

But I wonder if we shouldn't push to the JS side just the portion(s) of the screen being changed

Yes, we certainly need to do this.

In other words whenever something is changed on a screen line the line is send down to JS to be updated.

We should think about this problem in rectangles or changed regions, not on a line by line basis. It is very common for us to update just a portion of the screen. This portion will often be broken over multiple lines but only a specific rectangular region of the screen will be updated: there will be characters to the left and right of that region that are not changed. The the example below, the O characters are the region of the screen being changed.

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXOOOOOOOXXXXXX
XXXXXXXXXXXXXXXXXOOOOOOOXXXXXX
XXXXXXXXXXXXXXXXXOOOOOOOXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

I don't want to have a line-by-line oriented approach because that will entail too many trips to the client. When a region changes, only that region should be sent down.

Likewise, I don't want to send the unchanged portions of the lines down to the JS client. Let's just send the changed portions and we can include enough data to tell the JS portion what the X and Y offsets are for the region (and the length of each row). With this, it is a simple effort to just draw the sent cells in the right place. There will be no need to track the diffs in JS, just at the P2J client side.

the next step is to send data to JS asynchronous using a separate thread to push messages.

OK.

2. Regarding UNIX statement, at this moment I have no idea how to solve. We will see after I will finished the improvements.
In my opinion this should be fixed on the P2J client code.

This will be a tricky one, because UNIX suspends the P2J Client and currently we don't have a way of capturing the native process input/output. We might need changes in the process launching code.

Indeed. I had thought of this interactive child process problem a few times during the early development stages but I neglected to write down that thought and now you have found my mistake.

The use of INPUT THROUGH, INPUT-OUTPUT THROUGH and OUTPUT THROUGH should be fine. These are all ways to execute a child process with redirected terminal.

The problem is in the use of the interactive terminal. Progress allows the 4GL to "shell out" to the child process and the child process controls the terminal. The user can then interact directly with the child process until they enter input that causes it to exit. Then control naturally returns to the 4GL code and the application's processing continues. There is no way for the user to switch back and forth from the child process to the 4GL. The child process can only be launched, used and then terminated.

There are 2 problems that I see here:

1. Since the user is actually at the JS client (and not on the system on which the child process is being executed), we must find some way to feed the JS user's input to the child process AND capture the child process' output and display it on the JS client. We CANNOT use terminal redirection because such a mode changes the terminal to disable features that are needed by many interactive programs. In other words, these child processes may be programs that do not operate on a stream basis, but which truly manage the terminal processing on an entire screen/keyboard basis. The good news is that we already manage this launching process using native code and that is where we will have to implement our solution. The bad news is that we will have to figure out how we to insert ourselves into the terminal driver chain to allow us to intercept output and insert input. But we do have a program to potentially use as a starting point: screen. This program does pretty much exactly what we want to do. Please inspect its source code to see what techniques it uses and determine if we can use these same techniques to solve our problem.

2. How do we suspend the P2J client while still continuing to communicate with the JS client. At least this problem is fully under our control, because it is just about the P2J client design. I expect those asynchronous I/O threads will be very important to this solution.

But what happen when the client are using the ChUI web client and he is on another machine?

The shell, other process launching and even file reading/writing is always relative to the P2J Client, not the P2J Server. So if the P2J Client is on another machine, it must open a shell on that machine, not the server's.

Luckily, this is already exactly how we do this. Nothing more is needed to implement this, since we already have a separate P2J client process and that process can already use the libp2j.so/p2j.dll to launch a child process. This works today, even for the web client.

When we provide "remote launching" support such that the P2J clients can be launched on a separate machine from the P2J server, then the process launching will automatically be done on that other system, by virtue of the fact that the P2J client already manages the child process launching. Nothing extra is needed for this to work.

#406 Updated by Marius Gligor about 10 years ago

Passed regression tests. Committed to bzr revision 10471.

I created an Majic environment on my work station based on the converted project from devsrv01 testing environment.
Now I'm able to start both the ChUI native and ChUI web clients on my workstation.

My conclusions running Majic:

1. The server shut-down works properly. Using ./server.sh -k i10 for example results in a silently shut-down of the server.
I tested the same command to shut-down the two server instances on my devsrv01 testing environment and it works also properly.

2. Killing the spawned client "by hand" results in a client redirection to login page.

3. Terminating or auto terminating the Majic application results also in a client redirection to the login page.

4. Admin console works properly with the new jetty libraries.

5. Web sockets works on both Firefox and Chrome (Chromium) web browsers maybe a little bit more faster on Google Chrome due to it faster JS engine.

6. Definitely we have to find solutions to improve the performances on ChUI web client.

#407 Updated by Marius Gligor about 10 years ago

Here is the first release containing my changes in order to improve the ChUI web client performances.
I did major changes in both Java and JS code in order to improve performances.

1. For web socket protocol I switched to a binary format for payloads except for color palette message which remains in text JSON format.
The network packages are now optimized in term of size.

2. The messages are send asynchronously. First the messages are stored on a FIFO queue. From this queue the messages are extracted by a thread and pushed down to JS client.

3. The screen buffer is scanned line by line and only the cells which has changes on character, attribute or color are send to JS client.

4. I did also changes in JS code to be fastest as much as possible.

5. Based on some Internet articles regarding HTML5 canvas improvements I implemented a double buffered mode of drawing.
The screen is draw in an invisible canvas and finally the main canvas (visible) is updated.

6. The rendering problems are not yet fixed. I have to do a lot of tests here using different fonts.

7. Doing tests on Majic the results are comparable with the native client except for Majic login plug-in which is loaded in 2-5 seconds.
According to my tests the problem isn't from the JS code or from network traffic.
How I can use the Swing chui driver to do tests on Majic to see if we have the same problems?
I saw that the applications are rendered different on native on Swing (Web) chui drivers. It seems that native driver has more than 24 lines.
I obtained the fastest results using Google Chrome web browser. Today Google had issued a new release Chrome 33 which fortunately works fine.
The native is obviously fastest and we cannot obtains the same results using the web driver.

#408 Updated by Constantin Asofiei about 10 years ago

Marius, the changes are good and is a step forward into performance improvement. See bellow for my review:

ChuiWebSimulator
  • add javadoc for all the MSG_ constants.
  • document this protocol in the class javadoc (we might want to add it to our books, too).
  • triggerRepaint:
    - add a lazy initialization of the ByteArrayOutpuStream line var, so that we don't build a new instance for lines which weren't change.
    - can you think of a (fast) way of merging contiguos cells with the same attributes (text/color) into a single packet? I'm thinking about something like this: rectangle(x, y, widht, height) has these (text, color, etc) attributes. This way, a screen cleanup (when we need to set all cells to the same value) can be easily sent to the remote side, and the remote side can do a single color-fill for the specified rectangle, instead of drawing each cell. I think this would improve a lot when we need to draw borders or just empty content.
  • please enhance the terminal size to i.e. full-screen and check the JS performance. Use a test like this, with some heavy drawing:
    def var i as int.
    def var n as int.
    /* replace "24" with your actual number of rows */
    n = 24 - 7.
    
    form i with n down frame f1.
    
    do i = 1 to n * 10000: /* adjust this to something smaller */
       if i mod n = 0
       then up n - 1 with frame f1.
       else down 1 with frame f1.
    
       display i with frame f1.
    
       hide all no-pause.
    end.
    
PushMessagesWorker
  • there should be a space between while and condition in run() method, as in while (running).
  • even if messages is a ConcurrentLinkedDeque, you should enclose this code in a synchronized block, so is atomic:
                synchronized (messages)
                {
                   if (messages.isEmpty())
                   {
                      // no messages on the queue. wait.
                      messages.wait();
                   }
                }
    

    Otherwise, we might lose notifications between the isEmpty and the lock acquisition.

Finally, each time you change something in the communication protocol (as with this update, when you added a new thread), please don't forget to double check the P2J server termination scenarios you mention in note 406.

How I can use the Swing chui driver to do tests on Majic to see if we have the same problems?

To test the swing client, add this client node to your client.xml and use client.sh to start it, as usually:

<node type="client">
   <client>
      <driver type="swing_chui_frame"/>
      <chui rows="24"/>
      <chui columns="80"/>
      <chui background="0x000000"/>
      <chui foreground="0xFFA500"/>
      <chui selection="0x0000FF"/>
      <chui fontname="monospaced"/>
      <chui fontsize="12"/>
   </client>
</node>

I saw that the applications are rendered different on native on Swing (Web) chui drivers. It seems that native driver has more than 24 lines.

You need to manually set your terminal to 24 rows by 80 columns, as in native mode, MAJIC (as 4GL does) uses the entire available screen.

except for Majic login plug-in which is loaded in 2-5 seconds.

I'll take a look at this tomorrow.

#409 Updated by Constantin Asofiei about 10 years ago

Marius Gligor wrote:

except for Majic login plug-in which is loaded in 2-5 seconds.

I think I know where the problem is: the web socket is not open until we accept the certificate and the page is loaded. In the mean time (while there is no web socket), the authenication plugin finished drawing the screen, but there is no web socket open - so it doesn't have where to send the screen (the pushWorker is null). This is OK, the screen will be fully drawn when the web socket is connected. The delay comes from the Jetty/web socket side/JS: it takes a while to bring up the connection, but once it does, the screen is drawn immediately.

BTW, I just noticed that you if a line has changed cells, you send them to the JS immediately: can you test if is faster to send all the changed cells at once, for the entire screen? And not just for a certain line.

#410 Updated by Greg Shah about 10 years ago

I just noticed that you if a line has changed cells, you send them to the JS immediately: can you test if is faster to send all the changed cells at once, for the entire screen? And not just for a certain line.

Yes, exactly. We already know that extra trips to the client will reduce performance. It is better to do more work on the Java side to batch up a larger update and send it with one round trip.

This was my intention when I noted:

I don't want to have a line-by-line oriented approach because that will entail too many trips to the client. When a region changes, only that region should be sent down.

#411 Updated by Marius Gligor about 10 years ago

Rectangles detection on screen entropies doesn't help us too much. Let me explain:

Having a pattern like:

XXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXX LINE 1 XXXXXXXXXXX
XXXXXXXXX LINE 2 XXXXXXXXXXX
XXXXXXXXX LINE 3 XXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXX

When we draw the text on the canvas we have to draw line by line in other words we have 3 text draws operations.
The only advantages is that we have a single rectangle clear instead three.
Another example:

XXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXX1XXXXXXXXXXXXXXXXXX
XXXXXXXXX2XXXXXXXXXXXXXXXXXX
XXXXXXXXX3XXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXX

In this case we have also to draw text line by line but a single cell at a time.
On the other hand an algorithm to detect rectangles is not so easy to implements and require a lot of computations.

However a simple approach which could improve the draw is to group all cells in a line, something like an rectangle but on an single line.
This is very easy to implements require few computations and finally we have also 3 text draws and 3 rectangle clear instead 1.
I just implemented such an algorithm and now I have to do tests.

#412 Updated by Greg Shah about 10 years ago

The 4GL approach to all of this processing is executed in a single process. When we designed P2J, we deliberately chose to implement a thin presentation engine approach to the client. But this meant that what was inherently a single system/single process was now a 2 process approach and for many customers it is also split across 2 systems. We found there were many cases where the performance of our approach was not comparable to the original 4GL. When we worked to improve performance, the majority of the improvements came from eliminating trips down to the client. This often came from sending more data down in one trip rather than splitting the same data over multiple trips. We found there was a massive improvement in this case. It was worth spending more time to gather up/process the data on the server so that it could be batched.

Now that we have split this particular driver over 3 processes and potentially up to 3 separate systems, I am willing to bet that this same approach is still very important.

Please do spend the time to calculate the actual rectangle that is changed. I'm confident that the extra processing time will be worth reducing the size of each change set in the majority of cases.

It is OK if the canvas drawing code still needs to draw line by line, that is independent of how many trips we take over the network from the P2J client to JS. It is the trips we are trying to eliminate, not the looping in the JS drawing code.

#413 Updated by Marius Gligor about 10 years ago

Did you mean to find all rectangles or only the biggest one?

#414 Updated by Greg Shah about 10 years ago

Start with the rectangle specified by the union of the changes. If we still need more optimizations past that, we can look at sending multiple smaller rectangles down in a second phase.

#415 Updated by Marius Gligor about 10 years ago

Please take a look over this changes. All the changes on the screen are sent on a single message over the network.
Each message is composed from packages. Each package contains the adjacent changed cells that should be draw
on the screen grouped by attribute and color. Each packet will be draw on a single shut.
According to my tests is faster even my DB connection is slow.
I have to think to a better solution if exists as you already suggested.
Also the rendering issue is still not fixed.

#416 Updated by Marius Gligor about 10 years ago

Cleaning JS code after so many changes. I will try also to simplify even more the JS code.
I think that is possible to not keep any status in JS just drawing.

#417 Updated by Greg Shah about 10 years ago

Code Review 0221b

The changes look good. I look forward to seeing the next changes (batching multiple rows in a single message and fixing the rendering issue).

#418 Updated by Constantin Asofiei about 10 years ago

Marius: we are running blind when we are trying to determine if there were errors when execvp launches the command. All we get is a status code, but no actual stacktraces/messages/etc, if i.e. java can't launch the P2J client because some other P2J client uses the same debug port or for some other reason (faulty claspath, incorrect syntax for the command, etc). I know you've added some code in ClientDriver to redirect stderr for web clients (btw, I think we need to redirect stderr in case of P2J processes, too, not just web clients), but that code is used only for a successful command... if the command fails, its output and errors are lost.

On a side note: with #1787, we will be determining if a P2J client has its standard output or input redirected. This is useful for appservers, as this is how the agents work: all statements generating output are sending it to standard output (bypassing the 4GL infrastructure), which is redirected to a file. So: can we redirect the standard output for the child process, in spawn? Or better said, if we directly invoke spawn with output redirected (i.e. spawn ... > file.log), will this redirection be picked up by the child process?

#419 Updated by Marius Gligor about 10 years ago

The syslog is used by the audit function in spawn.c to log errors during the process spawning.
When the spawner starts first a child process is created.
The child process became a session leader and is detached from his parent.
The parent end execution here and exit.
Than the session leader process create than another child process.
The session leader will wait until child exit to avoid creation of orphan processes.
A pseudo terminal (PTY) is assigned to the child process and STDIN STDOUT and STDERR are redirected to PTY.
The STDIN MUST be redirected to PTY otherwise the test isatty() in process_linux.c line 91 fails.
This was a bug which was fixed recently if you remember.
I'm not sure but you could try to redirect STDOUT for the child process by putting a comment in spawn.c line 261.
As long as STDOUT is inherited from initial process to session leader and than to further to child process the redirection should works.

#420 Updated by Constantin Asofiei about 10 years ago

Marius Gligor wrote:

The syslog is used by the audit function in spawn.c to log errors during the process spawning.

Does this mean that errors during command invocation by execvp should end up in syslog? If so, please do some tests with a faulty classpath (so that the main class can't be found), and post here how the syslog messages should look.

#421 Updated by Marius Gligor about 10 years ago

On syslog only error messages related to system call's are stored.

syslog(LOG_ERR, "Error:%d method:%s (%m)\n", code, method);

What you describe indeed is not in the system log, the error message is lost.
The redirection of the child process STDOUT could be a solution.

#422 Updated by Constantin Asofiei about 10 years ago

Marius Gligor wrote:

The redirection of the child process STDOUT could be a solution.

I don't think we can redirect STDOUT by default, because this can cause conflicts in the 4GL behaviour. When writing from a 4GL batch program/appserver, behaviour is different with STDOUT redirected and with STDOUT not redirected; and we want to have full control of cases when we redirect STDOUT and when we don't.

#423 Updated by Constantin Asofiei about 10 years ago

PS: if the command has error messages, wouldn't these end up to STDERR? So, what about inheriting the STDERR from the P2J server; as ClientDriver.setWebClientLog redirects STDERR to file once the P2J Client is started, the STDERR will end up to the file, right? And P2J Server will see only the command-related errors, not from P2J Client.

#424 Updated by Marius Gligor about 10 years ago

A possible solution is to put in a command line parameter the name of the file we want to redirect the output.
We can open this file inside the spawner and redirect STDERR or STDOUT to this file.

#425 Updated by Constantin Asofiei about 10 years ago

Marius Gligor wrote:

We can open this file inside the spawner and redirect STDERR or STDOUT to this file.

This is good when we explicitly need to redirect STDOUT to a file (you can go ahead and start adding support for this), but I don't think is needed for STDERR. Try this in ClientBuilder.start:222:

pb.redirectError(ProcessBuilder.Redirect.INHERIT);

This should make the native code inherit the STDERR from the P2J Server. Let me know if it works.

#426 Updated by Marius Gligor about 10 years ago

It works. Nice! The messages from the client are stored now into the server log.

#427 Updated by Marius Gligor about 10 years ago

1. No longer dirty screens. I found what cause the canvas to becomes dirty and I fixed this issue.

When a text is drawn on the canvas the text characters are converted into graphical glyphs which are rendered on a paint area.
Sometimes the browser draw some pixels outside the paint area and the canvas becomes dirty.
Drawing of the letter R is one such case that I found.
The solution is to protect the outside area whenever we draw the text on the canvas by defining a clip rectangle as a paint area.
All paints are done only inside the clip rectangle and the outside area remains unchanged.
The HTML5 canvas API has support to define and use clipping regions.

At this stage the font metrics is poor implemented on HTML5 canvas API. It allows to measure a text width only.
I found a method to measure also the height of a font.
The text rendering differs on web browsers. I created a test page (font.html)
Please open this page on Firefox and Chrome and observe differences.
According to my tests in Widows OS the character height is the same in Firefox, Chrome and IE.
However the space at the top of the character is 1 in Firefox and 4 in Chrome and IE.
As a result the cell height on the canvas is 3 pixels smaller in Firefox and the screen aspect ratio is different in Firefox versus Chrome and IE.
On Linux OS the top is 0 on Firefox and 4 in Chrome. The character height is the same.
By measuring the font height I eliminated the browser dependencies on this issue.

2. Looking to improve data transfer from client to JS code I designed an improvement of the current implementation.
Before starting to implements I would like to describe what is already done and what I would like to do next.

- drawing on canvas are expensive operations.
- the number of canvas text drawings must be reduced to a minimum value.
- drawing texts on the canvas is possible only one line per each draw operation.
- each draw operations are done in 2 steps. First the paint area is filled with the background color (fillStyle, fillRect) then the text is drawing with the foreground color (fillStyle, fillText).
- we have to send down to JS only the changed screen cells that have to be redraw.

In order to minimize the number of drawings the adjacent screen cells in a line having the same attributes and color are merged in a single text string having the same attributes, stored in a package and appended to a message. Finally the message is send down to JS code. The message contains packages from all screen lines and each package is optimized for drawing. On the JS the packages are extracted from the message and draw one by one on the canvas. This is already implemented and looks like Swing screen driver implementation. (ChuiSimulator#paintComponent).

A step forward is to detect rectangles containing screen cells having the same attributes and color by keeping in mind that we need to have a minimum number of drawings.
Here we have to be carefully. Let see some cases:

a). Patterns like:

 XXXXXXXXXXX
   XXXXX
XXXXXXXXXX

could be packaged as follow:

XXXXXXXXXXX  XXXXX XXXXXXXXXX (3 rectangles with height=1)

which require only 3 drawings (3 fillRect and 3 free fillText) or

XX  XXXXX XXXX
    XXXXX
XXX XXXXX XX

which require 7 drawing (7 fillRect and 7 fillText) operations and some extra computation to split lines.
On this case the optimized mode is to send 3 packages for 3 drawings.

b) Patterns like:

XXXXXXXXXXXX
XXXXXXXXXXXX
XXXXXXXXXXXX

or

X
X
X
X
X

are the best candidates to be send as rectangles. When such a rectangle is drawing on the canvas
the clipping rectangle is bigger and we have a single fillRect operation and 3 fillText operations.

This part (detection of rectangles) is not yet implemented and I would like to implements on the next step.
Please let me know if you have any other ideas regarding this step.

#428 Updated by Marius Gligor about 10 years ago

Overall best performances has been obtained using Google Chrome which really has a very fast JS engine.

#429 Updated by Constantin Asofiei about 10 years ago

Marius Gligor wrote:

This part (detection of rectangles) is not yet implemented and I would like to implements on the next step.

Please postpone this one a little and first build a separate update with following:
- the STDERR fix for spawn.
- add support in spawn.c to redirect the STDOUT to a file; the file name will be received from the arguments.

This is priority work needed by the #1787 and #2228 tasks.

#430 Updated by Constantin Asofiei about 10 years ago

PS: add support for a clientConfig/outputToFile node, with the name of the log file. This flag will be read by ProcessBuilderOptions but not by WebClientBuilderOptions. ProcessClientBuilder will pick up this flag and send it to the spawn tool, via an argument; make sure both syntaxes support it: spawn 1 for password auth and spawn 0 for P2J secure connection auth.

Think of a way to include additional info in the file name (i.e. process ID, P2J user, appserver name). Do you think append mode (>>) can be easily added? If yes, we can use a clientConfig/appendToFile node, to mark the append mode.

#431 Updated by Marius Gligor about 10 years ago

OK. A first question please. This change remains or was only for test purposes.
pb.redirectError(ProcessBuilder.Redirect.INHERIT);

#432 Updated by Constantin Asofiei about 10 years ago

Marius Gligor wrote:

OK. A first question please. This change remains or was only for test purposes.
pb.redirectError(ProcessBuilder.Redirect.INHERIT);

This change needs to remain; otherwise it will be almost impossible to determine why a P2J client couldn't start, when there are problems with the command.

#433 Updated by Marius Gligor about 10 years ago

I understood the STDERR is redirected foe both modes 0 and 1 so I kept the code.
The difference between > and >> is reflected in the open file mode.
On > the file is always created on >> we will try to use an existing file.
This could be implemented by having an aditional command line parameter which instruct the spawner how to open the file.
How could I do tests using the mode 0 )with no password)?

#434 Updated by Constantin Asofiei about 10 years ago

Marius Gligor wrote:

How could I do tests using the mode 0 )with no password)?

Mode 0 is used for starting batch processes or appserver clients. Here are the steps to configure your directory for a batch process:
  1. configure a process account for your directory; there are two steps:
    - in security/accounts/processes
              <node class="process" name="p2j_proc">
                <node-attribute name="enabled" value="TRUE"/>
                <node-attribute name="alias" value="p2j_proc_alias"/>
                <node-attribute name="description" value="a P2J batch process"/>
                <node-attribute name="server" value="FALSE"/>
                <node-attribute name="master" value="FALSE"/>
              </node>
    

    - in server/default/runtime, to set the p2j-entry, use this node:
              <node class="container" name="p2j_proc">
                <node class="string" name="p2j-entry">
                  <node-attribute name="value" value="com.goldencode.testcases.SomeProgram"/>
                </node>
                <node class="integer" name="stop_disposition">
                  <node-attribute name="value" value="2"/>
                </node>
              </node>
    

    Is important to replace SomeProgram with a converted program which IS NOT USER-INTERACTIVE. Use a program with something like this:
    def stream rpt.
    
    output stream rpt to some-file.txt.
    message "some-test".
    output stream rpt close.
    

    Currently, you can't use DISPLAY or MESSAGE to write. Instead, you can edit the converted program by hand and add some System.out.println and System.err.println statements, to check where the output will go. BTW, while you are at it, please fix ClientDriver to redirect STDERR for batch processes, too; currently, this is redirected only for web clients.
  2. Use the -c option of the ServerDriver to re-configure your directory's certificates and private keys. At some point, it will ask you if you want to save the server certificates in a file; enter srv-certs.store as the file name. This is the only important step; leave all the private keys in the directory, so it will be easier to run.
  3. install the files needed by spawner's "no-password authentication" mode. In this mode, it needs to connect to the P2J server and extract the command. Beside the spawn tool, you need in the same folder: the p2j.jar and srv-certs.store files, both with 0550 modes. If everything is setup correctly, spawn 0 will output the syntax. BTW, I think the puts calls in spawn.c will go to STDOUT, not STDERR: please change these so that they will write to STDERR.
  4. start the server normally.
  5. to start a batch process, you can use the -b p2j_proc arguments for the ServerDriver. You can create a separate server.sh file which hard-codes the -b p2j_proc and use this to start the batch process. If successful, this will:
    a. connect to the target P2J server and instruct it to start a batch process
    b. the target P2J server will call spawn, which will:
    - connect back to the P2J server using a secure connection and the temporary credentials (see NativeSecureConnection)
    - if connection was possible, use RemoteSpawner.getCommand (implemented by ClientBuilder.getCommand) to retrieve the real command
    - pass this command back to the native side of spawn, which will execute it. The P2J client for the batch process will use the same temporary credentials to retrieve the trust-store and the process key-store from the P2J server. These will be used to authenticate the batch process.

#435 Updated by Greg Shah about 10 years ago

Code Review 0224a

Your use of the direct pixel access in canvas for the calculation of font metrics is very clever. I love it!

The changes in tty.screen.js are good. Based on the described changes, I did expect some code for the Java side too, but the only file that was different in the update was tty.screen.js. Are there some files missing?

#436 Updated by Marius Gligor about 10 years ago

No files are missing. I did changes only on JS code.
On the next step I have to implements the rectangle detections on Java code. This work require also some changes on JS code.
On my previous post I described the next step that I have to do and I specified that is not yet implemented.
My expectations are to obtain better results after implementation of rectangles detection.

#437 Updated by Greg Shah about 10 years ago

OK, I understand.

#438 Updated by Constantin Asofiei about 10 years ago

Marius: can you tell me the changes needed in spawn.c, to redirect the child process to a file? I don't need the full support, I just want to hard-code the output to a file and test some of the #1787 changes made by Eugenie.

#439 Updated by Marius Gligor about 10 years ago

You have to delete the lines when the STDOUT and STDERR are redirected to PTY.
The open the file on which you want to redirect the STDOUT or STDERR and redirect to file descriptor.
I already implemented the redirection and now I have to configure my environment and do some tests.
So far I tested "by hand".
Please take a look over my current implementation.

#440 Updated by Constantin Asofiei about 10 years ago

About 0225a.zip:
  • ClientBuilderParameters
    - can you think of a way to pass the PID in the file name, too? Maybe %pid% can be replaced by spawn.c?
    - all process launching is done from a thread with the server's context, so SecurityManager.getUserId will always return the server's id. But, the process name is known to the ProcessClientSpawner. Add a ProcessBuilderParameters subclass of ClientBuilderParameters which saves the process name passed to ProcessClientSpawner. Add a ClientBuilderParameters.getUser() method, which by default returns SecurityManager.getUserId() and will be overridden by subclasses to return the correct subject ID, for each case: i.e. web clients the system username and P2J processes the process name.
    - use SecurityManager.getAppServerForProcess to determine if this has an appserver or not; and replace %appserver% accordingly.
    - if outputToFile is not set, we do not want to redirect.
    - also, I think is best for each sub-class to prepare the filename on its own. So, the ClientBuilderParameters.getOutputToFile logic will treat only the %userid% and %timestamp% cases and subclasses can resolve the other cases (i.e. %appserver%).
  • ClientDriver
    - rename setWebClientLog to setClientLog
    - I think is enough to check only for the server:spawner:uuid config: we can assume that all clients started by the P2J server receive this config.
  • ProcessBuilderOptions
    - the javadoc for outputToFile is incorrect
    - you need to default to null if the outputToFile node is missing - we don't want to redirect always.
    - you are duplicating logic from ClientBuilderParameters.getOutputToFile. All logic should be in ClientBuilderParameters.getOutputToFile and related subclasses.
  • spawn.c
    - why do you redirect STDERR to a distinct file? I thought the pb.redirectError(ProcessBuilder.Redirect.INHERIT); is enough... as we want the STDERR from spawn.c to end up in the server's log, right?
    - I'm not sure I understand why you are using spawn:stdout:file to pass the log file name... the spawn tool should receive only one additional argument in spawn 0 syntax, which is the name of the file to which STDOUT needs to be redirected and this argument needs to be optional. In spawn 1 case, we don't want to redirect the STDOUT.

#441 Updated by Marius Gligor about 10 years ago

It was a miss understanding in requested design. Let clarify.
What I understood is that outputToFile is a file to redirect STDERR on case 0.

You said that this is not true and the correct is:

The redirection of STDERR is the same on both cases and is done on ProcessBuilder.
The outputToFile is a file to redirect STDOUT on case 0.
The redirection of STDOUT is optional and could missing.
No redirection of STDOUT on case 1 so spawn:stdout:file should be eliminated.

We have 3 process ID's. If we need to put the id of the child process on the file name we have to do the
redirection on the child process before execvp.

Please let me know if this is the correct design.

#442 Updated by Constantin Asofiei about 10 years ago

Marius Gligor wrote:

You said that this is not true and the correct is:

The redirection of STDERR is the same on both cases and is done on ProcessBuilder.
The outputToFile is a file to redirect STDOUT on case 0.
The redirection of STDOUT is optional and could missing.
No redirection of STDOUT on case 1 so spawn:stdout:file should be eliminated.

All are correct.

We have 3 process ID's. If we need to put the id of the child process on the file name we have to do the redirection on the child process before execvp.

I have these processes on devsrv01, after I connect with a web client:

ca        3934  0.0  0.0  41548  1528 pts/28   Ss+  05:32   0:00 /home/ca/testing/spawner/spawn 1 ca /home/ca/testing/majic/run/client java -Dlbin=/home/ca/testing/majic/lbin -DHOME=/home/ca -Djava.library.path=/home/ca/testing/majic/p2j/build/lib/ -classpath /home/ca/testing/majic/p2j/build/lib/p2j.jar com.goldencode.p2j.main.ClientDriver server:spawner:uuid=3e2699fc-467b-45bd-b448-017659318890 net:server:host=localhost net:connection:secure=true net:server:secure_port=3373 client:driver:type=chui_web client:chui:rows=24 client:chui:columns=80 client:chui:background=0x000000 client:chui:foreground=0xffa500 client:chui:selection=0x0000ff client:chui:fontname=Monospaced client:chui:fontsize=12 client:web:socketTimeout=-1 client:web:watchdogTimeout=-1 client:web:port=10473 client:web:host=localhost web:referrer:url=https://localhost:7473/chui
ca        3935  109  0.1 21863464 98760 pts/28 Sl   05:32   0:18 java -Dlbin=/home/ca/testing/majic/lbin -DHOME=/home/ca -Djava.library.path=/home/ca/testing/majic/p2j/build/lib/ -classpath /home/ca/testing/majic/p2j/build/lib/p2j.jar com.goldencode.p2j.main.ClientDriver server:spawner:uuid=3e2699fc-467b-45bd-b448-017659318890 net:server:host=localhost net:connection:secure=true net:server:secure_port=3373 client:driver:type=chui_web client:chui:rows=24 client:chui:columns=80 client:chui:background=0x000000 client:chui:foreground=0xffa500 client:chui:selection=0x0000ff client:chui:fontname=Monospaced client:chui:fontsize=12 client:web:socketTimeout=-1 client:web:watchdogTimeout=-1 client:web:port=10473 client:web:host=localhost web:referrer:url=https://localhost:7473/chui

I guess the spawn here is the parent process, right?

#443 Updated by Marius Gligor about 10 years ago

Indeed the spawn is the session leader process the parent of the child process, our client.
The 2 remaining processes are the session leader and his child, our client.
The parent process of the session leader (the initial process created by Java ProcessBuilder) is no longer running.
After the session leader process is created it exit.

#444 Updated by Constantin Asofiei about 10 years ago

Marius, some more issues/notes:
  1. adding System.err.println calls directly in the java program will write to the server log (which is OK, as we are on the server side). I forgot about this when I mentioned it in note 434.
  2. I've hard-coded the redirection after switching to the working dir, just before execvp (as you mentioned) and Eugenie's update on #1787 seems to work OK. But I'll keep testing it a little more.

#445 Updated by Eugenie Lyzenko about 10 years ago

Marius, Constantin,

I need some clarification for output redirection.

Do we need to check STDERR in addition to STDOUT to find out if the output is redirected? Is it possible to have STDERR redirected but STDOUT - not or STDOUT redirected and STDERR - not, these cases must be considered as redirected? Another word we are considering the output is redirected when at least one of the standard output streams is redirected, correct?

#446 Updated by Constantin Asofiei about 10 years ago

Eugenie Lyzenko wrote:

Do we need to check STDERR in addition to STDOUT to find out if the output is redirected?

No. I don't think 4GL even allows access to the STDERR (from 4GL code). Running a program in batch mode using pro -b -p run_batch.p 2> a.txt behaves the same way as there was no redirection. Only STDOUT redirection looks like is picked up.

#447 Updated by Marius Gligor about 10 years ago

No. it's not necessary to check STDERR redirection before redirect the STDOUT and vice versa.

All 4 cases are possible:

STDOUT and STDERR not redirected.
STDOUT and STDERR both redirected.
STDOUT redirected and STDERR not redirected.
STDOUT not redirected and STDERR redirected.

#448 Updated by Eugenie Lyzenko about 10 years ago

Running a program in batch mode using pro -b -p run_batch.p 2> a.txt behaves the same way as there was no redirection.

Thanks, this is exactly what I wanted to know. Because if this was not a true I had to add STDERR checking into native call that detect redirection.

#449 Updated by Constantin Asofiei about 10 years ago

Marius, something else to think about and add to your web client issue list: once we are redirected, there is nothing stopping us from copy-pasting the URL on another browser/tab/machine. This is also a security issue, because if someone guesses the port, it will be able to see that port's active web client without having to authenticate; I've just tested this on two machines:
- on machine A I've logged in and started the web client
- on machine B I've copy-pasted the URL and I can see the web client. After this, the terminal on machine A seems to freeze (as all refreshes are sent to machine B); but if keys are pressed - they will end up to machine B's screen.

We need to secure the redirected URL, so that only the authorized browser session can use it.

#450 Updated by Marius Gligor about 10 years ago

I'm trying to start a batch process. I did all configurations but when I started the batch client I got:

Error connecting to server
javax.net.ssl.SSLException: Connection has been shutdown: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

#451 Updated by Greg Shah about 10 years ago

Once a web socket is open, we shouldn't allow any other connections. Likewise, only the browser that initially is redirected should be allowed to connect in the first place. Perhaps we need to pass a token (a different one than we use between the server and P2J client) in the redirection URL?

#452 Updated by Marius Gligor about 10 years ago

I have to found a solution to set-up the embedded server on the client to accept only one connection.

#453 Updated by Constantin Asofiei about 10 years ago

Greg Shah wrote:

Perhaps we need to pass a token (a different one than we use between the server and P2J client) in the redirection URL?

Using a token is OK, but this one I think it needs to be generated by the JS side when the user enters the credentials and pushed to the server via POST. Both sides will save this toke and after redirect, each time the web socket sends or receives a request (on both P2J client and browser side), it must receive a token and reject any requests with an unmatched token.

This way, the P2J client can receive requests only from the authorized browser and viceversa.

#454 Updated by Constantin Asofiei about 10 years ago

Marius Gligor wrote:

I'm trying to start a batch process. I did all configurations but when I started the batch client I got:

Error connecting to server
javax.net.ssl.SSLException: Connection has been shutdown: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

Can you send me the directory.xml and srv-certs.store files you used?

#455 Updated by Constantin Asofiei about 10 years ago

I forgot to mention: remove all the security/keystore and access/password nodes from server.xml and client.xml.

#456 Updated by Constantin Asofiei about 10 years ago

See the 0225b.zip update from #2124: I forgot to change the code in ServerDriver.connect, for the case when the private keys are read from the directory.

Beside this, you need to add the security:truststore:alias=standard in the server.xml, so that the server connection is validated.

#457 Updated by Marius Gligor about 10 years ago

The certificate problem is now fixed. However I have another problem.
When I started the batch client an error message "Invalid user name." I found inside the server log file. This message comes from spawner.

#458 Updated by Constantin Asofiei about 10 years ago

Marius Gligor wrote:

The certificate problem is now fixed. However I have another problem.
When I started the batch client an error message "Invalid user name." I found inside the server log file. This message comes from spawner.

Add a server/default/runtime/p2j_proc/clientConfig node with this content:

<node class="string" name="systemUser">
   <node-attribute name="value" value="mag"/>
</node>

#459 Updated by Marius Gligor about 10 years ago

Now the redirections are works but I still have an error. The batch process cannot be started.
Tomorrow I have to do more tests to find out why this happen.
The Batch.java is the result of batch.p conversion. Is the node "p2j_proc" properly configured?

<node class="container" name="p2j_proc">
  <node class="string" name="p2j-entry">
    <node-attribute name="value" value="com.goldencode.testcases.Batch"/>
  </node>
  <node class="integer" name="stop_disposition">
    <node-attribute name="value" value="2"/>
  </node>
  <node class="container" name="clientConfig">
    <node class="string" name="systemUser">
      <node-attribute name="value" value="mag"/>
    </node>
  </node>
</node>

For outputToFile I used a node like this:

<node class="string" name="outputToFile">
  <node-attribute name="value" value="client_%pid%_%appserver%_%userid%_%timestamp%.log"/>
</node>

The palce holders are replaced except appserver because I have no application server.
The pid is replaced inside the spawner code.

I attached also my changes.

#460 Updated by Constantin Asofiei about 10 years ago

Marius, the entry point for the process in directory.xml needs to end with .execute (i.e. the method which we need to run). This should get you past.

About 0225b.zip: your changes look good; see my comments bellow:
  • ClientBuilderParameters
    - you've replaced all "at" characters with "%"
  • ProcessBuilderOptions
    - line 115: you have a typo in "place holder" and some extra spaces on line 112
  • ProcessBuiderParameters
    - change the c'tor to accept a ProcessBuilderOptions instead of ClientBuilderOptions.
    - please centralize the logic of replacing the placeholders - move the %appserver% from ProcesSBuilderOptions c'tor to getOutputToFile.

#461 Updated by Marius Gligor about 10 years ago

The outputToFile parameter is an optional command line parameter. In the future it is possible to add more optional parameters.
In order to parse multiple optional parameters I think that a best idea is to send a pair of command line parameters like:
-O <outputToFile>
or a single argument like:
-O:<outputToFile>
What do you think about this?

#462 Updated by Constantin Asofiei about 10 years ago

Marius Gligor wrote:

The outputToFile parameter is an optional command line parameter. In the future it is possible to add more optional parameters.
In order to parse multiple optional parameters I think that a best idea is to send a pair of command line parameters like:
-O <outputToFile>
or a single argument like:
-O:<outputToFile>
What do you think about this?

I like the idea, go ahead with it.

#463 Updated by Marius Gligor about 10 years ago

Here are the latest changes and the results of my tests with and without STDOUT redirected to a file.
The STDERR is always inherited from server.
I tested both 0 and 1 options for spawner and found to work properly.

#464 Updated by Constantin Asofiei about 10 years ago

Marius, the update is OK. Please put the ProcessBuilderOptions.getProcess together with the other public methods. You can go ahead with regression testing.

#465 Updated by Constantin Asofiei about 10 years ago

Marius, remember the stress drawing test on note 408? Well, I've ran it with your 0224a.zip update and it broke the browser. Why is that: although PushMessagesWorker sends the messages in sync, the browser receives (and interprets) them async. What I did to conclude this:
  • I've placed a breakpoint in the browser, tty.screen.js:375 - the drawText function
  • I've placed a breakpoint in PushMessagesWorker.run:114 and stepped over to send the message to the browser.
  • the browser was in breakpoint at drawText. Removed the breakpoint in PushMessagesWorker, let the thread run.
  • the browser started drawing other text, although I was still blocked in drawText for the very first message.

I know the program on note 408 is not some normal usage, but this might hurt us anyway. I'm not sure, but at some point there was some mentioning that the P2J client was waiting for the browser side to finish, before returning the control back to the P2J server side: did you change the sockets on the JS side to be async in the mean time?

#466 Updated by Greg Shah about 10 years ago

did you change the sockets on the JS side to be async in the mean time?

JS is single threaded (unless worker threads are used; in our case they aren't) BUT to eliminate the natural problems that occur due to the single threaded nature, it is highly asynchronous. Many APIs, including websockets are designed as a set of asynchronous callbacks. There is nothing we can do to set or not set this for websockets.

If I understand the problem, we just need to make sure to always apply the screen changes IN ORDER, right? So we just need some sequencing information passed with each message and then honor that sequencing info.

#467 Updated by Constantin Asofiei about 10 years ago

Greg Shah wrote:

If I understand the problem, we just need to make sure to always apply the screen changes IN ORDER, right? So we just need some sequencing information passed with each message and then honor that sequencing info.

Yes, this would solve the drawing order. We can use a priority queue on JS side to sort the requests in real-time, but we will need to ensure that JS waits for the next message (in sequence) to arrive, before pulling the next request from the queue. Also, if the JS is not processing the requests fast enough (i.e. consider my drawing stress test) and the queue fills up with lots of pending requests, can this destabilize the browser?

I wonder if is not better to synchronize the PushMessagesWorker with the JS side: wait for the JS to inform that it finished processing the request before sending the next one.

#468 Updated by Marius Gligor about 10 years ago

What you ask me to do I'm afraid that is impossible in JS.
Unlike Java the JS is a very simple programming language having a lot of limitations like:
- It's not OO oriented. The OO is simulated by a construction name closure.
- JS is a functional language and accept other functions as parameters for functions.
- JS is not multithread. We cannot create threads and synchronize the execution of threads.
- The JS engine is under the browser control and as developer we have no access to the core engine.
- JS is very slow and performances are accordingly. Google Chrome has the faster JS engine and it use a JIT compiler to obtain a better speed.

Please read:
http://www.sitepoint.com/multi-threading-javascript/

#469 Updated by Constantin Asofiei about 10 years ago

If we don't sequence the drawings, what I'm worried about is that the screen can end up in the wrong state: i.e. if message M1 needs to draw on the same area as message M2 and M1 is drawn after (or at the same time) as M2, then the screen will not be in the correct state. As I understand, the web socket onmessage is an event executed when the socket receives something, and this event is pushed to the main JS thread, to be executed, right? And this is why the browser keeps executing other incoming messages, even if I had a breakpoint for the first message: this suspends the execution of this JS code, and not the entire JS thread.

Anyway, what about the other approach I mentioned: is there any reason for the PushMessagesWorker not wait until is notified by the JS that it finished drawing?

#470 Updated by Greg Shah about 10 years ago

is there any reason for the PushMessagesWorker not wait until is notified by the JS that it finished drawing?

I don't think this is needed. It should be pretty easy to only apply the messages in order.

If a message comes in with an out-of order sequence number, then it can be added to a sparse array using the sequence number as the subscript.

When a message comes in that has the next expected sequence number, then this will be drawn AND we can continue drawing any already received messages that have contiguous following sequence numbers. This is easy to detect by subscripting the array. Of course, each drawn message is cleared from the array and as soon as the next expected sequence number is not found in the array, the drawing stops.

#471 Updated by Marius Gligor about 10 years ago

WebSockets is built on TCP. TCP guarantees delivery and ordering of packets. In addition, unlike TCP, WebSockets is message based which means that WebSocket messages are received as an entire message (TCP is streaming and 'messages' may get fragmented from the listener's perspective)

#472 Updated by Greg Shah about 10 years ago

TCP has nothing to do with it. I agree that the entire message will be received before it is delivered and if that message takes more than 1 TCP frame, those frames will be put together into the full message before delivery.

The problem is the asynchronous nature of JS. Just because the messages are sent (and presumably received via TCP) in a known order doesn't mean that the websockets callback function is called in the same order on the JS side. I'm guessing that the websockets messages are being delivered to our code out of order when there is network stress.

If that is not the cause of the problem, then please do debug into the stress test and find the real problem.

#473 Updated by Hynek Cihlar about 10 years ago

Also, if the JS is not processing the requests fast enough (i.e. consider my drawing stress test) and the >queue fills up with lots of pending requests, can this destabilize the browser?

If a message is not lost and JS will not process the incoming messages fast enough, your network buffers will eventually fill up which will cause TCP flow control to slow or stop the far end from writing more data to the socket.

#474 Updated by Marius Gligor about 10 years ago

Sorry I think that is my fault. I found a serious bug inside my code which is the cause of describe bevaviour.
By mistake the message for the entire screen is send on each line ;-( The send message remains inside the loop instead outside.
I did some tests and I added a sequence to the draw message.
Good new is that the messages are delivered in right order.
I have to prepare a package having my changes I will send to you.

#475 Updated by Marius Gligor about 10 years ago

Here are the changes containing the fixed bug. You can use JS console to see the order of the packages.
This part will be changed on rectangle implementation.
Obviously the drawing performances are also improved.
Please do some tests using this changes and let me know your results.

#476 Updated by Marius Gligor about 10 years ago

The package was send just for testing purposes. Please do not code review.

#477 Updated by Constantin Asofiei about 10 years ago

Marius Gligor wrote:

Please do some tests using this changes and let me know your results.

I'm using Firefox 26.0 - for Ubuntu canonical 1.0. Although the update is an improvement, the drawings are still done async and the browser stops responding at some point (as I guess it can't keep up with the drawings). Have you ran the stress test? How does it behave for you?

You can use JS console to see the order of the packages.

OK, they seem to be in order, until I place a breakpoint in tty.socket.js:66, just at the start of the onmessage callback: messages still be received and processed, even if this one is paused. After I resume it, from the JS console:

08:50:21.354 "message: 10505" tty.socket.js:82
08:50:22.326 "message: 10506" tty.socket.js:82
08:50:22.330 "message: 10507" tty.socket.js:82
08:50:32.320 "message: 10329" tty.socket.js:82

-------

OK, I've just installed latest chrome and did some tests: the behaviour is a lot different, in a good way; the stress test is even faster than swing. The drawings are in sync and a breakpoint in onmessage suspends the entire JS thread, not just that message. So I guess what I saw in firefox is a difference in JS implementation between browsers.

#478 Updated by Marius Gligor about 10 years ago

Implements rectangle detection on screen cells that should be update on JS code.
With a few extra computation in Java rectangle detection is a step forward on improvements.
This time I tested carefully.

#479 Updated by Marius Gligor about 10 years ago

Implements STDOUT and STDERR redirection for spawner Ref. #2124
Passed regression tests. Committed to revision 10483

#480 Updated by Marius Gligor about 10 years ago

Here are my last cahanges after Majic tests on devsrv01.

- I added character \u2502 (|) to measure the height of the screen cell. The result is an improvement especially when Firefox is used since we provide more space between lines.
- I did some small changes to protect the canvas to became dirty when draw text lines.
- I added specific javadoc comments in all JS scripts.
- I added javadoc on ChuiWebSimulator.triggerRepaint method like follow:

When data is prepared to send down to browser JS code to be draw the data should be
optimized for drawing on canvas.
<p>
The video screen memory is composed form cells stored in a 2 dimensional matrix,
The size of the matrix is rows x columns where rows are the number of rows in the screen
and columns are the number of columns. For example 24 x 80 for a screen having 24 rows
and 80 columns. Each cell is a structure which holds cell attributes, color and character.
The status of video screen memory is cached and preserved the last screen status.
Whenever a new screen should be send down the new screen is compared with the cached screen
and only the changed cells are send down. Also the cached status is updated to reflect the
latest changes. The cell is considered changed when at least one of the cell properties
attribute, color or character had changed.
<p>
Drawing texts on HTML5 canvas using JS code is done on two steps:
<ul>
<li> First the background rectangle containing the text is filled with the background color.
The largest the filled rectangle is the drawing is optimal.

<li> The second step is to fill the rectangle with the containing text using the foreground
color. On HTML5 canvas this can be done only on a line basis by filling with text
rectangles having the height equals to one character. An optimized draw on this case
require a text length as much as possible long.
</ul>
<p>
When a network message is constructed this is done on two steps by keeping in mind the
performances constraints described above.
<ul>
<li> The new screen that have to be send is scanned line by line column by column to detect
changed cells. Adjacent changed cells on a line having the same color and attributes are
stored together on a Chunk structure. The chunks are stored on a linked map structure having
the cell Color (attribute and color) structure as a key and a linked list of chunks as value.
Both the linked map and the linked list preserve the inserting order.

<li> On the next step the message is build and contains all chunks from a screen detected on
the first step. For each entry on the map the list of chunks is used to build rectangle
structures. The chunks are extracted from the list one by one. If multiple chunks could be
combined on larger rectangles the chunks are combined to form a largest rectangle. The width
of a rectangle is the number of characters from a chunk and the height are the number of chunks
combined. For example a rectangle of 25x5 holds 5 lines of 25 text characters each.
All characters have the same color and text attributes. The condition for two chunks to form a
rectangle is to have the same length, to start at the same position within the line and are from
adjacent lines. Chunks that cannot be combined as large rectangles are stored inside the message
as simple rectangles, rectangles having height equals to one.
</ul>

#481 Updated by Marius Gligor about 10 years ago

Just a minor change. Use \u2501 (-) character to measure the width of a cell.
Character \u2501 should be the largest. However I observed that sometimes some dirty pixels still remains on the screen.
I observed that drawing character & on a 22 font size cause such kind of effects despite our clipping rectangles definition.
Nevertheless I have to do more tests and investigations.

#482 Updated by Constantin Asofiei about 10 years ago

Marius, the changes in 0228b.zip look good to me. Greg: please take a look too.

#483 Updated by Marius Gligor about 10 years ago

1. During this weekend I did a lot of tests on Linux and Windows OS. Defining clip rectangles when drawing text lines does not protect 100% against dirty screens.
Depending on the text pattern which is draw dirty screens could be seen.
In order to have 100% clean screens we have to protect the screen each time we draw a character in other words to draw the text line character by character.
This approach does not affect the drawing performances in a visible way, so the application remains fast and the screen remains always clean regarding the text pattern we draw on the canvas. Also for OS compatibility I used the \u2500 and \u2503 characters to measure the screen cell dimensions.

2. I started to fix also the security issue related to multiple access. The security issue is implemented at two levels.

a) On successful login before client redirection a cookie containing an authorization token is added to the response object.
A SHA message digest is generated from the random UUID and used as an authorization token.
On the client embedded server when the browser try to load the main page should present the cookie containing the authorization token.
If the cookie is missing an 404 error is returned.
This will protect the application when multiple accesses are requested from different instances of web browser/browsers.

b) The number of simultaneous web sockets connections is set to 1 by a simple logic implemented in ChuiWebSimulator using an integer count.
This protect the application against possible "attacks" comings from entities who stolen the authentication token.
Also this provide protection when the application is accessed within the same browser from other tab pages.

Although this works fine from 3-4 hours I'm struggle to find a solution for the following scenario:
The user is redirected and the Majic login plug-in is presented waiting to enter credentials.
If at this point we reload the page using the browser "Reload" button the screen is clear but the Majic plug-in never occur again!!!
After entering credentials and Majic conversion application is running using the browser "Reload" button works properly, the page is reloaded.

#484 Updated by Constantin Asofiei about 10 years ago

Marius Gligor wrote:

A SHA message digest is generated from the random UUID and used as an authorization token.

Is best to use some new random UUID, and not reuse the UUID from the P2J client.

b) The number of simultaneous web sockets connections is set to 1 by a simple logic implemented in ChuiWebSimulator using an integer count.

I don't think this is secure enough: after an onClose, until onConnect is called again, the connection is open for anyone to access! And if someone else steals the connection, with your current changes the authorized user can no longer get it back, until the unauthorized one disconnects. We need to authenticate each incoming web socket connection, not just limit the number of connections.

If at this point we reload the page using the browser "Reload" button the screen is clear but the Majic plug-in never occur again!!!

The login client is stuck in a WAIT-FOR loop, which doesn't refresh the screen anymore. If the browser is refreshed, there are no more screen draw events in PushMessagesWorker, so there is nothing to draw. In these cases, you need to determine that a browser page refresh is in progress and send the entire screen down to the browser.

After entering credentials and Majic conversion application is running using the browser "Reload" button works properly, the page is reloaded.

Main menu in MAJIC refreshes the screen every 5 (I think) seconds, to update an upper-right clock.

#485 Updated by Greg Shah about 10 years ago

Code Review 0303a

1. About the authorization approach:

I agree, we cannot reuse the spawn UUID. As I noted in note 453:

Perhaps we need to pass a token (a different one than we use between the server and P2J client) in the redirection URL?

Using a token is OK, but this one I think it needs to be generated by the JS side when the user enters the credentials and pushed to the server via POST. Both sides will save this toke and after redirect, each time the web socket sends or receives a request (on both P2J client and browser side), it must receive a token and reject any requests with an unmatched token.

Some important points:

  • Inserting the token in a URL is not the safest approach since this can be stored in the browser history which is not the best place for sensitive data.
  • If the JS side generates a unique token and passes it during the POST at the time of the LOGIN (chui_web.html), the question is: what is the best way to get access to it from the page to which we are redirected? The data cannot be embedded in that page, because then the first browser to load that page would work (but that isn't necessarily the same browser for the user that did the OS-level login, which is a massive security problem).
  • We can't use the "Web Storage" (the localStorage and sessionStorage) API because the login document (chui_web.html) will have a different origin than the index.html that will be returned from the client Jetty instance.
  • The use of cookies is likewise not possible because of the same origin issue. In addition, we want to avoid using cookies because they may be limited/blocked in many browsers. If we do have a need in the future, we can use the Web Storage API, which is well supported in recent browsers.
  • The reason I want to have the JS side generate this and POST it is because then the JS side will have the unique token and only that user's browser instance will be able to authenticate with the associated spawned jetty client.
  • There is a technique called "cross-document messaging" which is well supported in current browsers. You can invoke Window.postMessage() and communicate between 2 documents of different origin. Please look at this as a potential solution. My only concern is whether this will work in a redirect case since the login page (chui_web.html) will be replaced by the JS web client page (index.html) and the pages may not simultaneously exist to allow such a communication.

2. I'm still thinking about the PushMessagesWorker changes. Can you describe the reasons for the changes? I think the approach is probably OK.

3. Chunk.java has some methods that are missing javadoc (hashCode() and equals()).

#486 Updated by Marius Gligor about 10 years ago

1. Today I did many tests and investigations regarding the "Refresh" button on web browser so far unfortunately without success.
All that I found is that after the button is pressed the web socket connection is closed and the code code is 1001 0n Firerfox and 1005 on Chrome.
What's strange is that happen only when the p2j Majic plug-in refresh. After Majic login everything works fine.
I remember that some times ago I tested this issue and I found to works!

2. You suggest me to find another solution without using cookies. Here is a possible solution the we can try to implements on JS code. We have two web servers M (main) and C (client).

- The user access the main server M and get the login page as usual.
- Using a JS AJAX call send a POST message (submit the login form) containing the user name, password and a random generated token down to server M. We could use DOJO or jQuery to make AJAX calls.
- On successful login the target URL on server C (redirection url) is returned into the AJAX response. On error the error message is returned and displayed on the main page.
Also the authorization tag is exported to server C when the server key store is exported.
- The client is no longer redirected. The main page on server M contains also an iframe on which the target page from server C will be loaded.
Also is possible to open a new window (pop-up) to load the page from server C.
I found this useful article regarding cross domain messages [[http://blog.teamtreehouse.com/cross-domain-messaging-with-postmessage]]
Than using a cross domain communication postmessage the generated token is transferred to JS web sockets code from the main page.
After the token is received the web socket connection is open. On web socket open the token is send down to server C.
On server C if the token match the exported tag the web sockets session is used if not the session is closed.
If the page from server C is loaded directly the web sockets connection remains closed and will not affect the running code.

The problem regarding the browser refresh button remains as well. Please let me know your opinion.

#487 Updated by Greg Shah about 10 years ago

What's strange is that happen only when the p2j Majic plug-in refresh. After Majic login everything works fine.
I remember that some times ago I tested this issue and I found to works!

Did you previously test from the MAJIC login screen? The screen created by the authentication plugin is not a "full" client and there is no corresponding server-side converted code that is running at the time this is executed. This means that the "normal" environment is not yet fully functional and in fact, the user's P2J "session" is not yet fully created since this is the authentication step for the session setup.

Please review the code in aero.timco.majic.security.LoginClient.java (you can see it in your devsrv01 ~/testing/majic/src/ directory).

I think you will have to debug into this code to see how it is reacting to the refresh.

Please also try CTRL-C on this screen. This may also be instructive, however I presume that the issue is really about how the session management works when it is disconnected during authentication.

Here is a possible solution the we can try to implements on JS code. We have two web servers M (main) and C (client).

I like it. Using an iframe is a good solution (and actually, I like it better than the redirect solution). This should also make it easier to "go back" to the login page on any disconnect.

Of course, the login portion should be hidden and the iframe must display as the "content" of the page, once connected.

The problem regarding the browser refresh button remains as well. Please let me know your opinion.

Yes, I would expect this to be present in either approach.

#488 Updated by Greg Shah about 10 years ago

Also is possible to open a new window (pop-up) to load the page from server C.

Please avoid pop-ups. Just make the iframe visible and position it to be the primary content for the screen. The login form should be hidden.

#489 Updated by Marius Gligor about 10 years ago

1. I found and fixed the issue related to Majic login plug-in.
When Majic plug-in run within ThinClient using system logger cause application blocking.
For example on the following snippet from ChuiWebSimulator when the call LOG.logp is executed the application remains blocked forever!!!
As a result no other threads can acquire the lock on keyBuffer. More generally seems that calling static methods cause the same effect.
Writing simple messages to STDERR works. When I tried to build a message using a String.format this remains also blocked!!!
So I eliminated all log messages from all affected classes ChuiWebSimulator, PushMessagesWorker and WatchdogTimer.
For example with log messages in place the WatchdogTimer remains blocked and never force the client exit!
As a result now the Majic plug-in is loaded instantly on first load as well as on page refresh.
The WatchdogTimer and PushMessagesWorker are working properly.
I attached a version using a random generated token exported via server key store and used to set the cookie value.
The cookie is valid for a single client session only.
This is a complete solution using redirection and cookies for authorization.

@OnWebSocketClose
public void onClose(int statusCode, String reason)
{
  synchronized (keyBuffer)
  {
      if (--connections == 0)
      {
    // Stop push worker thread
    stopPushWorker();
    // Start watchdog timer
    startWatchdog(watchdogTimeout);

    // here remains blocked            
    LOG.logp(Level.INFO,
          "ChuiWebSimulator.onClose",
          "",
          LogHelper.generate("Connection closed status=%d reason=%s", statusCode, reason));
      }
  }
}

2. Regarding the JS solution I identified two major issues:

a) Supposing that client is authenticated and the application is loaded and running into the iframe.
When the user reload the page using browser button or the keyboard (F5) the application is gone away forever.
The main page is reloaded from main server M and the user should start a new session.
It is not possible to stop this user action on web browsers using JS.

b) The main server M became heavy loaded if all users are connected to this server.

#490 Updated by Greg Shah about 10 years ago

Code Review 0305a

1. In HtmlResourceHandler, in what case will isAuthorized() find that authorizationToken is null?

      // if authorization token is null don't check
      if (authorizationToken == null)
      {
         return true;
      }

This is an unsafe default behavior. The default behavior must always be to refuse access if things are not configured properly.

2. ChuiWebSimulator, PushMessagesWorker and WatchdogTimer classes need comments to explain that the logging should not be used and why this the case. The comments need to be in the places that can be affected.

3. In ChuiWebSimulator, you have added an improper usage of volatile.

   /** Keyboard input queue */
   private volatile Deque<Integer> keyBuffer = new ConcurrentLinkedDeque<>();

The construction of keyBuffer is only done during this line. That means that there is no case where keyBuffer will refer to an instance that can change at any time. The use of volatile here suggests that this is being made safer in some manner, but in fact it makes no difference since the reference itself is never going to change. Since the volatile property does not extend to the contents of the Deque and only affects the keyBuffer reference itself, this is confusing/dangerous. It may lead future readers to think it is safer than it is.

4. The ChuiWebSimulator.connections member has lost its javadoc.

5. WebHandler:

If the instanceof protection is really needed, then you will never get to that code because you will have already raised a ClassCastException:

         Object store = (ServerKeyStore) webSpawner.getServerData();   // will raise ClassCastException

         if (store instanceof ServerKeyStore)  // if you get here you already know that store is a ServerKeyStore
         {
            // Use UUID to build a SHA message digest as token. 
            Cookie cookie = new Cookie(WebHandler.AUTHORIZATION_TOKEN, 
                                       ((ServerKeyStore)store).getAuthorizationToken());
            // add cookie
            response.addCookie(cookie);            
         }

6. Please confirm my understanding of how this works:

a. the M server generates an authorization token, stores it in the specific ServerKeyStore instance for the new client
b. the M server spawns the new client, which reads the authorization token (which is unique to that specific client instance) and as part of the initialization of server C, it configures server C to expect that token
c. when server C is ready, the M server inserts this token as a cookie in the redirect response and redirects the original browser session to server C
d. the original browser will read the redirect response and this will generate a GET request to the server C
e. the GET request will have the cookie set EVEN THOUGH the request is going to a different origin (server C) than that in which the cookie was generated/set (server M)
f. the request seen by server C has the cookie set (if and only if the request was the redirected GET from the original browser) and the authorization passes

The part I am really unclear about is step (e). It seems to me that the cross-origin nature of the cookie should break this step.

Please explain exactly how the redirect is working such that the cross-origin cookie is OK.

7. I'm also unclear on how long the cookie will remain set for the resulting page loaded from server C. In other words, what is the scope of this cookie (when does it go away)?

#491 Updated by Greg Shah about 10 years ago

a) Supposing that client is authenticated and the application is loaded and running into the iframe.
When the user reload the page using browser button or the keyboard (F5) the application is gone away forever.
The main page is reloaded from main server M and the user should start a new session.
It is not possible to stop this user action on web browsers using JS.

I think it would be possible using Web Storage. We could easily store the authorization token and redirect url there which leads the page to immediately reload the iframe instead of making the form visible.

b) The main server M became heavy loaded if all users are connected to this server.

This one is definitely more concerning to me. I still wonder if we couldn't find a way to replace the current window's URL while setting the authorization token it that context. Once that is done, we don't have to be in an iframe environment anymore.

Anyway, if you have worked out all issues with the redirect/cookie implementation, we will go with that.

#492 Updated by Marius Gligor about 10 years ago

1. Done. HtmlResourceHandler the base class of ChuiWebPageHandler is used to load other pages like the admin page which does not require authorization.
I changed the implementation and I moved the authorization code only on ChuiWebPageHandler.

2. Logging use should be totally avoided anywhere inside this classes ChuiWebSimulator, PushMessagesWorker and WatchdogTimer.
So I added a comment to the class javadoc.

3. Done.
volatile keyword tell that the variable is accessed by multiple threads and has two effects:
- In Java read/write operations are atomic for reference variables and for most primitive variables except long and double.
Using volatile the read/write operations are atomic for all variables.
- The volatile also prevent variables to be cached by threads.

see http://docs.oracle.com/javase/tutorial/essential/concurrency/atomic.html

4. Done

5. Done.

6. This is almost exactly with a few remarks:

a. the M server generates an authorization token, stores it in the specific ServerKeyStore instance for the new client
b. the M server spawns the new client, which reads the authorization token (which is unique to that specific client instance) and as part of the initialization of server C, it configures server C to expect that token
c. when server C is ready, the M server inserts this token as a cookie in the redirect response and redirects the original browser session to server C
d. the original browser will read the redirect response and this will generate a GET request to the server C
e. the GET request will have the cookie set EVEN THOUGH the request is going to a different origin (server C) than that in which the cookie was generated/set (server M)

On the first redirection the origin is the server M but when the user reload the page by pressing refresh button the page is reloaded and the origin became C.
Unfortunately this differ on web browsers. Chrome keep the referrer to server M on all requests Firefox keep the referrer header to server C.
On both cases the authorization token should be presented by the web browser. Cookies was invented exactly to do that. The cookies are always presented into HTTP headers regarding referrer.
A possible improvement here might be:
- Accept the authorization key if the cookie token match and change dynamically the token and the cookie value.
Whenever the page is loaded and cookie token match the token on server C the authorization token is changed again.

f. the request seen by server C has the cookie set (if and only if the request was the redirected GET from the original browser) and the authorization passes

The part I am really unclear about is step (e). It seems to me that the cross-origin nature of the cookie should break this step.

Please explain exactly how the redirect is working such that the cross-origin cookie is OK.

7. I'm also unclear on how long the cookie will remain set for the resulting page loaded from server C. In other words, what is the scope of this cookie (when does it go away)?

The life of the cookie is the browser session. When the web browser is closed the cookie is destroyed.
On the embedded server the cookie remains until server shutdown, client exit.

8. In a production environment the JS files could be minified into a single file something like tty_min.js and on index.html use this file instead the separate JS files.
This make the code difficult to read and minimize the amount of data to be downloaded when the page is loaded.

#493 Updated by Greg Shah about 10 years ago

Code Review 0305b

1. In ChuiWebPageHandler, please use * for imports.

2. Please remove the reference to "Majic" in the javadoc for classes ChuiWebSimulator, PushMessagesWorker and WatchdogTimer. We must not have customer-specific information in our documentation for P2J. The plugin you are referencing is a generic feature that can be called an "authentication plugin that is used during session establishment with the P2J server".

Otherwise I am fine with the code. Is there anything left to do?

#494 Updated by Greg Shah about 10 years ago

If your testing is not showing any noticeable performance differences between Majic in the browser and Majic in the terminal, then you can stop your performance improvement work now.

8. In a production environment the JS files could be minified into a single file something like tty_min.js and on index.html use this file instead the separate JS files.
This make the code difficult to read and minimize the amount of data to be downloaded when the page is loaded.

Yes, we will keep obfuscation in mind for the future. I think we will leave this as is for now, especially since we will have many more changes to this area for GUI support. Getting GUI running is the top priority.

#495 Updated by Marius Gligor about 10 years ago

Here are the changes after your last code review.
In the mean time I prepared the testing environment by doing a complete build of Majic project on devsrv01 having the latest changes.
Using the jar files of this Majic converted release I did the final tests on my workstation and I found no problems.
I think that we could go forward and start the regression tests.

#496 Updated by Greg Shah about 10 years ago

I'm fine with the code in 0306a. Go ahead with runtime regression testing.

This should close off #2244, right?

Please work #2243 next and then #2247.

#497 Updated by Marius Gligor about 10 years ago

OK. I started the regression tests on devsrv01.
Regarding #2243 if I understood better we could encounter a problem.
I'm afraid that in Windows OS it's not possible to create a process on another account using only the user name.
We must provide both user name and password. Without credentials we can create processes only for the account on which the server (spawner) is running.

#498 Updated by Greg Shah about 10 years ago

We must provide both user name and password. Without credentials we can create processes only for the account on which the server (spawner) is running.

Nasty. After 3 decades it is sad to see that there is such poor support for non-interactive use cases. I guess we will have to store the OS password for such accounts in our directory. It should be optional (not needed on UNIX or Linux) and it should be stored with the account information.

Constantin: what do you think?

#499 Updated by Greg Shah about 10 years ago

Constantin: How does the 4GL handle this for appserver launching? Does it always assume everything runs in the same context (as the parent)?

#500 Updated by Constantin Asofiei about 10 years ago

Greg Shah wrote:

I guess we will have to store the OS password for such accounts in our directory.

This can work, but ProcessClientBuilder needs to be changed so that it will access spawn.c in "password-mode", if the account has a system password.

It should be optional (not needed on UNIX or Linux) and it should be stored with the account information.

I agree it should be optional, but I don't think is needed to be kept at the account. The system user is read from the i.e. runtime/default/runtime/<account-id>/clientConfig/systemUser path (for both user and process accounts). So, is best to keep it with the system user, in clientConfig/systemPassword node.

Constantin: How does the 4GL handle this for appserver launching? Does it always assume everything runs in the same context (as the parent)?

The appserver launching in 4GL can be setup to launch agents either in server's account or in a specific account (by setting the system username and password). This is done at the Broker. Our current model is OK, as the agents can be started in a specific account.

#501 Updated by Marius Gligor about 10 years ago

ChUI web client improvements ref. #2244
Passed regression tests. Committed revision 10487.

#502 Updated by Marius Gligor about 10 years ago

Here are the winspawn.c changes that I did in order to integrate the code from Linux spawn.c version.

1. In order to build spawn.exe you should do:

- Set JRE_HOME environment variable to a JRE installation. Example: JRE_HOME=F:\jdk1.7.0_21\jre
- Copy the file jvm.lib from JDK installation folder to MinGW lib folder. Example:
copy /B F:\jdk1.7.0_21\lib\jvm.lib C:\mingw64\x86_64-w64-mingw32\lib\ /B
- In order to build spawn.exe you could do: ant native

- When the spawn.exe runs jvm.dll file is loaded. Windows should search for jvm.dll in order to load the library.
The simple way to set the jvm.dll location is to add to PATH variable the path to jvm.dll file.
Example: If the file is located at F:\jdk1.7.0_21\jre\bin\server\ we have to add to PATH variable the file location. PATH=F:\jdk1.7.0_21\jre\bin\server;…

2. Changes from Linux/Unix version to Windows. I found a lot of differences between Linux and Windows described bellow.

- Unlike Linux on Windows we have to provide also a password when we spawn a process on another account.
I added a new node clientConfig/systemPassword to store the user password. The password is added to the list of arguments
only for agents (mode = 0) which is send down via a secured channel (RemoteObject). For Linux/Unix this argument is totally ignored.
On Windows if the password is present the agent will be spawn on the specified account (user name / password)
If password is missing the user name argument which is always present must be the user name of the system account on which
the server (spawn.exe) runs. In this case the client is spawn on the server account. If user name does not match the current user name
the spawn is refused. Because sending null or empty passwords are not enabled an empty password is send as and empty quoted string "".

3. Redirection of STDOUT has some major differences that we have to discuss.

- On Windows we cannot execute part of the client process from spawn code like in Linux. We have to redirect the STDOUT before to client spawn. 
At the spawn moment the PID of spawned client is unknown because will be assigned later by Windows, so I used the spawn PID to replace the pid. on SRDOUT file name.
- In order to redirect STDOUT we have to create the file and pass the file handle to replace the STDOUT.
At the moment of the file creation the client is not running, we are still on the spawn process.
If the client is spawned on a specified account other than server account, spawn process must have enough rights to create the redirection file inside
the working directory of the specified account.

- The ChUI batch driver extend ChUI console driver. ChUI console driver on Windows is a JNI implementation.
The JNI implementation on terminal_win.c use STDIN, STDOUT system files of the client process to manage the terminal screen.
Those handlers must be console handles.
If the STDIN or STDOUT are not console handlers due to a redirection to an ordinary file or pipe the terminl_win.c throw an exception. The ChUI batch client won't work.
I fixed this issue by doing some changes inside terminal_win.c which allow the code to always use console handlers. Now the STDOUT redirection is possible.
(see [[http://msdn.microsoft.com/en-us/library/windows/desktop/ms682075(v=vs.85).aspx]])
However unlike on Linux I saw that at the end of the batch execution a screen having the following prompt
"Procedure complete. Press space bar to continue." is shown and the batch process wait for a keyboard event.
In other words in order to terminate the batch process the user should press a key.
Also Even if the STDOUT is redirected I'm not sure if the batch messages are written to this file or on console.
For example the message "some-text" is written on the redirected file on Linux but on the screen in Windows and not inside the redirected file.
Using System.out statement from ClientDriver is written on the redirected file which prove that the redirection is in place.

#503 Updated by Marius Gligor about 10 years ago

Regarding to STDOUT redirection in terminal_win.c it seems that we have to do more changes here.
On init console we have to find if STDOUT is redirected. If STDOUT is redirected we have to keep both two handles for console and for redirected file.
Whenever a character is written on screen we have to write also the same character to redirected file or just only to redirected file.
Maybe other informations could be useful like checking if is a batch client or not.

#504 Updated by Marius Gligor about 10 years ago

On console initialization STDIN and STDOUT are checked for redirection.
If STDOUT is redirected the redirected handle is kept and the characters are written to redirected handle instead console handle.
However looking inside the P2J project the batch mode seems to be not yet implemented on Windows OS.
A batch process should not interact with user in any way. For example waiting for a key to end a batch process is not a good idea.

#505 Updated by Greg Shah about 10 years ago

It is my understanding that batch most is mostly implemented for Windows. I think an exception is the support for message box.

Eugenie?

#506 Updated by Eugenie Lyzenko about 10 years ago

It is my understanding that batch most is mostly implemented for Windows. I think an exception is the support for message box.

Eugenie?

The batch mode in Windows support is on the same level as for Linux. It is mandatory to set up the batch mode in client.sh/cmd:

swing="client:driver:type=chui_batch client:driver:background=true" 

Without this settings the batch mode will not work. And BTW the ThinClient has API call to check if it is running in batch mode and even if the batch is in background.

#507 Updated by Marius Gligor about 10 years ago

1. I'm using a command like: com.goldencode.p2j.main.ServerDriver -b p2j_proc
This is not a batch mode?

2. The redirection of STDOUT won't work without my changes. I'm not sure that my changes for redirection are enough or should be more changes.

#508 Updated by Marius Gligor about 10 years ago

Some time ago I worked to implements the STDOUT redirection in spawn.c for Linux/Unix OS (see note 434).
Now I'm working to do the same in winspawn.c for Widows OS.
The only problem is that redirection of STDOUT does not work on Windows OS. It works on Linux/Unix after the changes on spawn.c
I provided a possible solution at low level by doing some changes in terminal_win.c but I'm not sure this is OK.
This is the only problem from me regarding this issue.

#509 Updated by Eugenie Lyzenko about 10 years ago

1. I'm using a command like: com.goldencode.p2j.main.ServerDriver -b p2j_proc
This is not a batch mode?

Until the directory.xml contains client-mode-batch == true this is not a batch mode from clients point of view.

#510 Updated by Greg Shah about 10 years ago

There is no batch mode for the ServerDriver because there is no concept of a real server in the 4GL. All 4GL sessions are clients (appserver agents, webspeed agents, batch clients and interactive clients). Because there is no concept of a server (let alone a batch-mode server), we have no concept of this either.

When we start appserver agents or batch mode clients from the server, these clients are automatically started in batch mode. Batch mode means that the terminal input and output is largely nullified.

The appserver agents are also started in "background" mode, which means that the output (stdout) is redirected. This can be optionally added for batch clients.

#511 Updated by Marius Gligor about 10 years ago

OK. I understood. Is about appserver agents running in batch mode. The appserver agents implementation is to spawn clients in batch mode.
If the STDOUT is redirected appserver agents runs in "background mode".

At the time when I did changes in spawn.c I didn't do any other changes in screen drivers Java or native C code and the redirection for STDOUT works on Linux.
I need the same behaviour for Windows OS as well but the redirection of STDOUT is not yet implemented in screen drivers code for Windows OS.
Maybe I should check the existing code to see how the "background" mode is implemented on Linux OS and try to provide implementation for Windows OS.

#512 Updated by Greg Shah about 10 years ago

the redirection of STDOUT is not yet implemented in screen drivers code for Windows OS.
Maybe I should check the existing code to see how the "background" mode is implemented on Linux OS and try to provide implementation for Windows OS

I guess so.

I must be mistaken, because I thought that it was your STDOUT redirection for spawn that properly created the background mode for appserver clients.

#513 Updated by Marius Gligor about 10 years ago

The STDOUT redirection is implemented for Windows OS at high level but not at low level.
So my changes in terminal_win.c should remains because is the low level STDOUT redirection except the writing on the redirected handle.
I have to eliminate the writing on redirection handle inside terminal_win.c because this is already done at high level.
Normally should work without any other changes but I sow that in Windows does not work. Maybe is a configuration problem or something else.
I'm working to do some debugs to see what's happen when the appserver agent is spawn in Windows OS.

#514 Updated by Marius Gligor about 10 years ago

I found the problem. I fixed my bug and now it works properly. What was missing is the last argument client:driver:background=true.
On Windows when the client is spawn we have to provide a single command line string not an array of strings like on Unix.
On my method which build the command line the last argument was ignored due to a small bug.
Now the behaviour is the same on Windows and Linux / Unix.

#515 Updated by Greg Shah about 10 years ago

Code Review 0311b

The code changes make sense.

My only questions are in regard to the terminal_win.c change:

1. In what circumstances does this code get triggered? (why is it needed now when it wasn't before)

2. Does this have any implications when using the interactive client? (manually launching ClientDriver with the "Console" ChUI driver)

#516 Updated by Marius Gligor about 10 years ago

1. The code inside terminal_win.c is the native implementation of chui_native screen driver for Windows OS using console API.
GetStdHandle(STD_OUTPUT_HANDLE) returns process STDOUT handle. Console API works only if this handle is for a character device.
If the process SDTOUT is redirected to a file or a pipe the chui_native won't work.
Before fixing my bug regarding background agents the chui_console was spawned instead a background agent and because the STDOUT was redirected to a file the spawned client did not work. However after my bug has been fixed I realized that batch clients and background agents does not use the terminal_win.c code and the redirection works without my changes.
My changes make possible to always run chui_native regarding if STDOUT or STDIN is redirected or not.
Since the redirection of STDOUT or STDIN for chui_native clients make no sense I think that is better to keep the original terminal_win.c code which prohibits redirection.

#517 Updated by Greg Shah about 10 years ago

Since the redirection of STDOUT or STDIN for chui_native clients make no sense I think that is better to keep the original terminal_win.c code which prohibits redirection.

Agreed.

#518 Updated by Marius Gligor about 10 years ago

Today I designed and tested a simple algorithm in C code to encode/decode the password on Windows OS.
I think that is better to keep the password in an encoded form in directory instead a clear-text.
What do you think about this?

#519 Updated by Greg Shah about 10 years ago

Normally, I would absolutely want to do this.

Can the encoding be easily reversed? It seems like it must be reversible in order to decode it for Windows. So this can help obscure the plain text, but anyone with access will still be able to recover it, right?

We already have a great deal of sensitive data in the directory, such as our private encryption keys. This file must be tightly protected such that only system admins have access.

We need to balance the value of obscuring the password with the cost of requiring the admin to specially encode it before setting up the configuration. If this was truly encrypted and could not be recovered, then I would say we would definitely do it.

#520 Updated by Greg Shah about 10 years ago

Code Review 0312a

The code looks fine. What is left to do with this task?

#521 Updated by Marius Gligor about 10 years ago

Here is my implementation for password encryption.
For Windows OS we have to keep the systemPassword in clear-text if the appserver agents runs on an specific account.
I think that is better to keep the password in an encoded format rather then clear-text.
We can do that in Java code or in C code inside winspawner.c
Because Windows OS require clear-text password we have to encrypt and decrypt the password using the same key, a symmetrical algorithm.
The problem is where the key for encryption should be store.
Using a Java helper class to encode/decode the password is not the good solution because Java classes could be easily decompiled and the key is no longer secret.
So I implemented a simple encrypt/decrypt algorithm in C code inside the winspawn.c
The reverse engineering of winspawn.c is more difficult than a Java decompiler. Also the spawn is a private tool.

The algorithm use 3 hard coded keys to encrypt/decrypt the password.
I added a new option in command line used to find the encrypted value for a specified password.

spawn 9 &lt;password&gt;

This option will display the encoded value for password on STDOUT or on a file if redirected.

spawn 9 &lt;password&gt; 1>password.txt

Just copy and paste the encoded value and put on directory as a password value.
Let me know your opinion.

#522 Updated by Greg Shah about 10 years ago

I like the idea.

Some issues:

  1. We can't use encryption keys that are hard coded in our source code. We provide our source code to our customers and in the future we may even open source it. This means that the encryption keys are not very secret. Instead we would have to use keys provided by the admin. These keys could be in a file that is stored with spawn and which has permissions that disallow anyone but the admin from reading it.
  2. Please provide a plain-text mode such that if the admin doesn't care to use this feature, we can still implement the system.

#523 Updated by Marius Gligor about 10 years ago

The encryption use is now optional. By default no encryption is used just clear-text.
In order to use encryption a configuration file spawn.ini should be provided on the same folder where spawn.exe is located.
Inside the configuration file on the Encryption section the use of encryption could be enabled/disabled
Inside this file the encryptions keys are provided as well.
Whenever spawn is running this file is loaded. If file exists defined options are used.
If no such file is found no encryption is used just clear-text.

#524 Updated by Greg Shah about 10 years ago

Code Review 0312c

I'm fine with the code.

Is there anything left to do for #2243?

#525 Updated by Marius Gligor about 10 years ago

No. I have no other changes to do.

#526 Updated by Greg Shah about 10 years ago

OK, then go ahead and get this runtime regression tested.

#527 Updated by Marius Gligor about 10 years ago

0312c.zip Integrate spawn.c changes into winspawn.c ref. #2243
Passed regression tests. Committed revision 10492.

#528 Updated by Marius Gligor about 10 years ago

1. The interactive client on Linux/Unix could be implemented using a pseudo terminal.
A pseudo terminal (PTY) is assigned to interactive process during creation.
The parent process will use master channel of PTY to communicate with child process connected to slave channel.
On the parent side we have to emulate a physical terminal like VT 100. The terminal emulator is the key
part of this system and should provide a full implementation of VT 100 specifications. The emulator
provide also rendering services for SWING or JS virtual terminals. Key strokes from keyboard are pushed downstream to child
process via master/slave channel. The outputs of the child process are pulled upstream via slave/master channel
processed by terminal emulator and finally rendered on the screen by SWING or JS virtual terminals.
The scheme bellow depict the communication chains. Also I posted some useful links related to this topic.

http://rachid.koucha.free.fr/tech_corner/pty_pdip.html
http://man7.org/linux/man-pages/man7/pty.7.html
http://man7.org/linux/man-pages/man1/screen.1.html

|--------------|  Key stroke   |---------------|         
|              |-------------->|               |
| JS render    |               |               |           Pseudo terminal PTY          |-----------------------|
|              |<--------------|    VT 100     |   OUT   |---------------------|   IN   |                       |
|--------------|  Screen cells |    xterm      |-------->|          |          |------->|    child process      |       
                               |               |    IN   |  master  |  slave   |  OUT   |     (os-command)      |
|--------------|  Key stroke   |   emulator    |<--------|          |          |<-------|                       |
|              |-------------->|               |         |---------------------|        |                       |
| SWING render |               |               |                                        |-----------------------|
|              |<--------------|               |         
|--------------|  Screen cells |---------------|

<----------------------- P2J client (parent) -----------------------><------- interactive (child) process ------>
<-------------- Java/JS code ------------------><---- JNI C code --->

2. VT 100 terminal emulator is the core part of interactive process implementation.
Besides processing the specific commands for a VT 100 terminal the emulator should manage a lot of screen buffers.
Imagine an interactive process on which the user open the nano or vi text editor in order to edit a file having hundreds of text lines.
At one moment only one screen buffer is displayed on screen inside a scrollable viewport.

|---------------------------------------|
| Screen buffers                        |
|                                       |
|                                       |
|                                       |
|                                       |
|                                       |
|---------------------------------------|
| Scrollable viewort                    |
|                                       |
|                                       |
|---------------------------------------|
|                                       |
|                                       |
|                                       |
|                                       |
|                                       |
|                                       |
|                                       |
|---------------------------------------|

The VT 100 terminal emulator should be a Java implementation. Searching on Internet I found two implementations in two different projects.
Bellow are the links to those projects.

http://www.jcraft.com/jcterm/
https://code.google.com/p/zterm/

3. On Windows OS no pseudo terminals are available. However using anonymous or named pipes something similar might be implemented.
Instead to use a PTY pair channels we have to redirect the interactive process standard files to a set of pipes which allow to communicate between
the parent and the child process.
The result is the input and output streams capture of the child process.
This kind of input/output capture of a process is widely used in IDE's. For example Eclipse IDE use java.exe command to compile
sources inside IDE. The input and output of java.exe are captured and displayed inside the IDE console view.
It's not usual to have text editors like vi or nano running on a console window.
However a nano version for Windows (http://www.nano-editor.org/download.php) is available.
For this kind of applications we couldn't capture the input and output streams.
On my last worked issue I just found that such kind of applications MUST use console handles and not pipe or file handles.

#529 Updated by Greg Shah about 10 years ago

A couple of quick points:

The VT 100 terminal emulator should be a Java implementation. Searching on Internet I found two implementations in two different projects.

For your information, we already use JTA (and JSch) for this purpose in our Harness project (the regression testing automation toolset). JTA can be found here:

http://www.javatelnet.org/space/start

The problem with it is that it is GPL, so we can't use it in P2J. Zterm is also GPL so we can't use that.

I am interested in how JCTerm will work. From a license perspective, it is fine because it is LGPL (and the JSch dependency is BSD).

On Windows OS no pseudo terminals are available. However using anonymous or named pipes something similar might be implemented.

Before analyzing the Windows solution, please do some tests to see what kind of ChUI support the 4GL provides on Windows. Interactive child process support may be different on Windows. In other words: does the child process really take over the console on Windows OR does it launch a separate "windows" with a new console OR do something else?

#530 Updated by Marius Gligor about 10 years ago

None of the existing Java VT 100 emulators can be used as they are.
We have to create a derivative work if license allow or just use as examples.
Maybe a good idea is to create our own VT 100 emulator. I have VT 100 specifications.

#531 Updated by Greg Shah about 10 years ago

None of the existing Java VT 100 emulators can be used as they are.

Please provide more details.

  • What are the problems?
  • What would we have to do to make them usable?

Maybe a good idea is to create our own VT 100 emulator. I have VT 100 specifications.

I am open to this idea. But first I need to understand the issues with the existing options.

If we were to go down this path, what would you estimate as the effort?

#532 Updated by Marius Gligor about 10 years ago

The attached examples are form projects which implements SSH standalone clients.
Those projects use the same interprocess communication via a PTY and need a terminal emulator.
We cannot use any of those products as jar dependencies because we need only a VT 100 emulator which should be integrated in our P2J design for interactive client.

Our interactive client need only a terminal emulator not an entire standalone SSH client.
The VT 100 implementation in JCTerm has an ugly code inside and VT 100 emulation is incomplete as is described on the product web site
http://www.jcraft.com/jcterm

We have to design our specific interfaces for P2J interactive client.
The VT 100 emulator could be designed by getting some code from JCTerm project and adapt to our needs.
Also we could provide the missing implementation from VT100 specifications.
This could take 3-4 days work efforts.

#533 Updated by Greg Shah about 10 years ago

The VT 100 emulator could be designed by getting some code from JCTerm project and adapt to our needs.
Also we could provide the missing implementation from VT100 specifications.

I don't want to mix code into our project from other projects, especially since it is licensed differently. Even with an LGPL license, any code that we merge together with that code would have to be handled differently (and open sourced). I don't have a problem contributing our code back to a larger project and/or open sourcing a VT100 terminal implementation of our own. But since we need such a tight coupling between the terminal code and our client, we can't mix that code together.

Without taking code from another project, what is the effort estimate?

In addition, I have these other questions:

1. Are there any native solutions that can be used as a library by our JNI code to handle this terminal emulation for us? Does Konsole (and the other native Linux solutions like Gnome Terminal) not have a library for terminal emulation?

2. What does screen do to handle this? Does screen implement terminal emulation?

#534 Updated by Marius Gligor about 10 years ago

1. I don't know I have to check if such libraries exists.

2. Screen is writen in C and YES he provide also a VT 100 terminal emulation but the code is designed to be integrated into the screen project which is a complex one. I attached the ansi.c file from screen project which
contains the VT 100 emulation just to see how it looks.

3. A Java design is the best solution in my opinion. Finaly we have to design also renderers objects for this emulator for both Swing and Web Sockets clients. Than we have to integrate this parts with the existing native code which launch the interactive client. So far such a native code exists only for chui console client. The design of a VT 100 emulator from scratch could take 4 to 7 days but I cannot estimate the entire work effort for interactive client implementation.

#535 Updated by Marius Gligor about 10 years ago

I didn't found any native library that could be used in our P2J interactive client.
Each application which need a terminal emulator has designed their own solution.

#536 Updated by Greg Shah about 10 years ago

OK, go ahead and write our own vt100 emulation layer, from scratch. Of course, it is OK to look at the other projects to see how to do things, but just don't copy any code.

Keep it as simple and clean as possible. Also: keep it as separate as possible from the rest of the client code. Design an interface that allows its usage without deeply coding linkages into its internals.

#537 Updated by Marius Gligor about 10 years ago

Here is the RC (release candidate) of VT-100 emulator. It is an implementation of DEC VT 100 video terminal specifications.
The Swing renderer part looks like P2J ChuiSimulator implementation.
Starting from this design we have to integrate this standalone interactive client into the P2J Swing and Web clients.

How to do tests:
In order to see how it works you should compile both Java and native artefacts. The native code is loaded from libp2j.so
After P2J build you have to start Konsole.java as a Java application. You should get a Swing frame and a $ sh shell prompt.
Than you could type any command like bash, ssh, mc, nano, vi etc exactly like on Ubuntu Konsole application.

If you want to compare how commands runs on Ubuntu console and on VT-100 emulator you should do:

1. Open a console window on Ubuntu (KUbuntu)
2. Set terminal type to VT100: export TERM=vt100
3. Start the Java emulator test application Konsole.java as a Java application. Don't forget to specify the native lib location using the JVM argument -Djava.library.path=
4. Now you could execute and compare the same commands on both Ubuntu Konsole and on VT100 emulator.

You may now compare the execution for different commands on Linux console and on our VT-100 emulator.

#538 Updated by Marius Gligor about 10 years ago

I fixed a minor bug when using colors.
We have to add 1 to FG and BG because according to the current P2J implementation color 0 is not used.
Colors indexes are from 1...8 not from 0...7

#539 Updated by Marius Gligor about 10 years ago

Add code to synchronize with child process exit.

#540 Updated by Greg Shah about 10 years ago

I'm struggle to understand the current implementation on interactive process for native ChUI client.

We don't need to integrate it for the native client because the support is already inherently there. By definition, the native client is directly connected with a terminal emulator to the Linux/UNIX system. Our native code just has to "get out of the way" ("suspend") while the child process takes over control of the terminal. When the process ends, we "resume" and use the terminal ourselves.

Only the Swing and AJAX clients need this extra facility.

#541 Updated by Marius Gligor about 10 years ago

I implemented the interactive process for Swing ChUI client.
When a Linux OS command is executed from the converted application the shell command is executed on a modal dialog using the VT-100 emulator.
In order to resume application the user must close the dialog by executing an exit command or just closing the dialog.

#542 Updated by Greg Shah about 10 years ago

Please document how interactive child process support works on Windows (in the 4GL). When you are running a ChUI 4GL session, does the same feature work there?

#543 Updated by Greg Shah about 10 years ago

Code Review 0326a - Part 1

I am splitting this into multiple parts because I don't have time to finish the entire code review in one sitting.

1. For any new JNI interface, we implement it with the following standard approach:

  • there is a single file for the JNI API itself, which is cross-platform (e.g. filesys.c); this file hides all the JNI nastiness AND it must be written with no platform-specific code
  • this common file is what is added to the standard list of files for javah (in build.xml) and in the SOURCES= line (in makefile)
  • all exported entry points in the library must be in common code
  • if the common functionality is dependent upon platform-specific implementation code, then we design an internal-use API that will be called from the common code; this API is defined in a .h file (e.g. filesys.h)
  • then we implement a platform-specific version of this API for each platform (today we only support Linux/UNIX and Windows)
  • these platform-specific files are stored in files that are denoted by their target system (e.g. filesys_linux.c for the Linux/UNIX code and filesys_win.c for Windows)
  • platform-specific code does not ever get added to the SOURCES= line in the makefile
  • platform-specific code does not use any of the JNI features (calling up to Java instance or static methods, reading/writing arrays in the Java heap, reading/writing data members of Java classes...); the API is designed only using datatypes that are available on a cross-platform basis
  • architecture and word-size differences are hidden using makefile defines and #ifdef conditional preprocessing
  • Windows code must be compilable with MinGW and can use both WIN32 API calls and C runtime functions that are available in MSVCRT
  • any common code should only use C runtime functions that are available in both the glibc and MSVCRT

2. Are process IDs always only 32-bit?

3. In the native method shell(), why are you calling GetIntField() before setting those fields? The value is not being used so this seems like an unnecessary round trip through the JNI layer (which can be costly).

#544 Updated by Greg Shah about 10 years ago

Code Review 0326a - Part 2

I have finished going through all the code. It is really impressive.

4. It seems that there may be some missing parts of the VT100 emulator support. For example, start/end alternate character set support. I don't have any idea how important the missing pieces are. Please describe the limitations of the emulator.

5. The primary thing I'd like you to try is to eliminate the separate Swing window (VT100SwingRenderer/VT100SwingKeyboard). There are 2 problems with the current approach:

  • It only works for the Swing driver and not for the AJAX ChUI driver.
  • It pops up a dialog which is a different behavior than the user's normally see. In the native client, everything is in the same emulator window since the entire app is executing remotely and everything is being pushed down to the emulator.

With the AJAX client we will want to fix this issue anyway. The idea is to map the rendering/key processing to the driver's standard interface instead of having separate code for this. I realize that the current interface may need changes to enable this. That is OK, but please do minimize any changes.

Much of the VT100SwingRenderer looks just like the code in the ChuiSimulator, so I am hopeful that the basic implementation is not far off, even though it may be tricky to figure out.

The key point here is to shift into a special mode where we use the ChUI driver code for rendering and key processing, while suspending normal 4GL processing. We already have the suspend/resume hooks implemented to the driver level (see ProcessDaemon.launch()) to enable this aspect of the solution. This was going to be needed for supporting the AJAX ChUI driver anyway, so it seems that so long as we make the same changes to the driver API for the Swing client, that it will work there too.

6. Please move the terminal package to com/goldencode/ instead of com/goldencode/p2j/. This code seems to be generic (not P2J-specific) AND we will probably want to use this code in the Harness in the future (replacing our use of JTA).

#545 Updated by Marius Gligor about 10 years ago

This archive contains my later changes for interactive process implementation.
It is not the final release is only an intermediate work and is posted just to accompany my remarks.
However it works as was initially designed on a separate window.

1. I implemented almost all DEC VT-100 specifications except those related to terminal hardware.
For example start/end alternate character set support selects the character set from an EPROM inside terminal.
Also some commands are used by service personnel to calibrate the VT-100 terminal.
This kind of commands cannot be implemented and are not used by applications.
For command line applications like sh, bash, ssh VT-100 terminal features are not used,this kind of applications are running on row mode only.
Other applications like mc, nano, vi are using special VT-100 features because they are working on screen mode.
According to my tests all this applications works properly.
The VT-100 is the most simple terminal that can be emulated. The limitations of VT-100 are 24 rows, 80 columns, 8 colors etc.

2. I implemented the interactive process according to P4G OS-COMMAND options SILENT and NO-WAIT.
Using terminal.suspend() on Swing screen driver never returns so I used another way to implements WAIT (see ProcessDaemon.lunch).

3. I implemented also the interactive process for Windows OS as well. Unlike on Linux the Windows shell cmd.exe INPUT/OUTPUT cannot
be captured because cmd.exe use internal console handles which cannot be controlled from outside.
However simple commands like dir, copy works fine. Also I found a small Windows application named choice which display
a prompt and wait for an user input. This kind of commands are working in interactive mode.
Another issue on Windows is related to pipes. Read and write operations on pipes are blocked operations.
Here I have to do a small workaround inside the native code to cancel pipe read operation when interactive process exit.

4. Next starting from the already designed and implemented code I have to do other changes to integrate the interactive process
with the existing Swing and Web client code as you suggested on the previous code review.

#546 Updated by Marius Gligor about 10 years ago

The interactive process implementation fo0r ChUI Swing and ChUI Web (AJAX) clients with minimum changes on the existing code.

#547 Updated by Marius Gligor about 10 years ago

1. Use ansi STARTUPINFO on shell_win.c
2. Merge changes on build.xml.

#548 Updated by Greg Shah about 10 years ago

Code Review 0417a

1. Java_com_goldencode_terminal_NativePty_shell() in shell.c has some possible resource leaks:

  • The pointers created with strdup() are never freed.
  • The terminal type string pointer that is obtained using GetStringUTFChars is not released if the code returns on line 73. This is most likely an OOM situation, but if the JVM recovers, there will be a remaining reference on the heap that can never be garbage collected.

2. Is the shell_win.c implementation compatible with how the 4GL supports this feature? In other words, is this really how Progress does it too? Are there any limitations or open issues?

3. The 8192 command line limit is one that is enforced by the Windows CMD.EXE shell itself. But CreateProcess() has a 32K command line limit. We use CreateProcess so the limit could be higher. Does the 4GL limit the command line to 8192? If so, I wonder if it is using CMD.EXE in some way.

4. Have you tested what happens (on both Linux and Windows) when P2J is in wait_pid() and the child process is killed?

5. Have you tested what happens (on both Linux and Windows) when P2J is in wait_pid() and the P2J process is killed?

6. I don't think you are properly duplicating the logic of the ProcessDaemon.launch() else block on line 191. The following seem to be missing:

  • An ERROR condition should be raised if the child process can't be launched.
  • Avoids zombie processes when wait false by still calling LaunchManager.pseudoTerminalWait(pid) via an asynchronous thread. This is needed on UNIX/Linux to ensure that the child process' resources get reaped when it dies. Your code does seem to unconditionally call wait_pid (MasterPty.await() called from MasterPty.Runnable.run()) but I worry about the implications of always doing this on a separate thread. What exactly is the original client thread doing while the child process is executing?
  • On UNIX/Linux in silent false mode the ThinClient.suspendedPause() must be called when the child process exits. The current approach in MasterPty.Runnable.run() does not duplicate this sufficiently. The ThinClient code has some important features like proper condition processing, which cannot (and should not) be handled in the terminal package.
  • ConditionExceptions should be propagated to the caller, when generated. Note that they can be generated in ThinClient.suspendedPause() by user input.
  • Other exceptions are silently eaten/ignored. You may already be handling this OK, but it is hard to see exactly how this may or may not work because of a lack of exception handling in key places like MasterPty.Runnable.run().

7. I would like to eliminate our Swing dependencies in com.goldencode.terminal. For example, I don't see why we need the Swing parts of VT100SwingRenderer. It seems to me that the ChuiSimulator should already be able to handle all the paintComponent() needs by just calling getScreenData() just like the web client does. This just makes the solution more complex, it duplicates code and pulls in Swing dependencies for the terminal package that seem to be out of place. The terminal package should be purely about launching the child process, attaching to it and providing an API for reading/writing keys and screens.

8. In line with number 7 above, the code in com.goldencode.terminal is still too tightly coupled to the com.goldencode.p2j.ui packages. First of all, there should be no imports of com.goldencode.p2j (or its sub-packages like .ui) from com.goldencode.terminal. If there are some data structures like Cell that more properly belong in the terminal package, then we can move them. But if there are just integration points for call backs, then such APIs must be abstracted and provided as an interface that can be implemented in the com.goldencode.p2j.ui packages and "client" code in those packages can register instances of themselves for call backs. The idea is to make the terminal package completely standalone. The only exception at this time is the dependency on libp2j. That can be left in place. But otherwise, the package should stand on its own.

9. There is missing javadoc in VT100Emulator.run().

#549 Updated by Marius Gligor about 10 years ago

I have no idea how Progress 4GL runs on Windows OS.
So far I have access only on the customer server but the Progress 4GL is running on Linux using a ChUI interface not on Windows.

1. How could I have access to a Progress 4GL for Windows environment?

2. When I designed the interactive process for Windows OS I tried to implements something like on Linux OS.
Looking at current implementation for native client on Windows OS the interactive process seems to run on a separate window.
However for our web client the interactive process should always use the browser for input/output (keyboard/screen) on both Windows or Linux OS.
How we can solve this issue? Please let me know if you have any idea?

3. The interactive process always execute an OS shell command: "sh -c" on Linux and "cmd.exe /c" on Windows. (see ProcessDaemon.prepareCommandLine)

#550 Updated by Greg Shah about 10 years ago

I have no idea how Progress 4GL runs on Windows OS.
So far I have access only on the customer server but the Progress 4GL is running on Linux using a ChUI interface not on Windows.

Uh oh. Don't you have the getting_started_with_p2j_development.html document (should have been provided on Sept 29, 2013)? That document describes the details regarding access to the customer's Windows 4GL Development Box (WINDEV01). You have full access to this system to run 4GL code. I have been assuming that all your Windows testing of 4GL behavior has been done on that system.

Now I am a bit worried about the Windows interactive child process code. It may not be compatible with the real 4GL behavior. Please connect to that system and do some tests to confirm how things work.

Looking at current implementation for native client on Windows OS the interactive process seems to run on a separate window.

Please check the real Windows 4GL behavior and document it here.

#551 Updated by Marius Gligor about 10 years ago

Sorry I have this file on my machine but I cannot find any instructions regarding the customer's Windows 4GL Development Box.

#552 Updated by Greg Shah about 10 years ago

Please see the Test and Development Systems section.

#553 Updated by Marius Gligor about 10 years ago

Here are the changes for Swing and AJAX interactive process implementation as a result of your first code review.

1. The com.goldencode.terminal package is now more abstract. No longer contains dependencies from other P2J packages.
2. When the child process is killed "by hand" the behaviour is like a process exit. (see attached picture for an OS-COMMAND statement with wait and no silent options)
3. When P2J process is killed by hand or normal terminated its child processes are also terminated.

#554 Updated by Greg Shah about 10 years ago

Code Review 0424a

This is a very good revision. It is almost there.

1. It looks to me like the majority of the VT100SwingRenderer is generic. I'd like you to consider if most of this class can be made into an abstract base class that exists in com.goldencode.terminal. Then you can implement the minimum P2J-specific portion in com.goldencode.p2j.ui.client.chui.driver.swing. The key idea here is that much of this logic really does belong in the terminal package (because other users of that package would have to duplicate the logic). We can leave a smaller abstract set of functions that are specific to the use case.

2. The onTerminalExit() call on Windows platforms is not supposed to display the message or pause in non-silent mode. See ProcessDaemon:

            // if not silent we display a default message and pause indefinitely
            if (!silent && !PlatformHelper.isUnderWindowsFamily())
            {
               lastkey = terminal.suspendedPause();
            }

This is something EVL added after testing on Windows.

#555 Updated by Marius Gligor almost 10 years ago

A. I created a generic abstract class for VT100 renderer.

B. Regarding Windows OS and AJAX interactive client:

1. In Windows OS the P2J native implementation of interactive client spawn the OS command on a separate console widow. The executed command looks like: cmd.exe /c <command>
On this case is no need to call ThinClient.suspendedPause() because the user can see the results of the command execution on the interactive process window.

2. For Swing clients we use the same window for both P2J client and interactive client. This is default behaviour on Linux/Unix OS.
Using the same window for P2J client and interactive client is better to use ThinClient.suspendedPause() which allow the user to inspect the results after command execution before switch back to P2J screen.

3. For AJAX clients which are in fact remote clients we cannot spawn the interactive process on a separate window. We have to capture the input/output of interactive process and send over the network to the remote JS client running on the web browser. However we have some restrictions here. Supposing that P2J client (embedded server) is running on machine A and the user browser in on machine B if the user spawn a command like konsole on Linux or notepad.exe on Windows the processes are spawned on machine A and the user cannot interact with the spawned processes! On AJAX clients only commands that runs on the interactive window like ls, dir, vi, nano, mc, ssh etc. could be used.

4. Another important limitation in Windows OS is related to capture the input/output of a process. This is possible an almost all processes except some processes which use console I/O.
For example cmd.exe cannot be executed as an interactive client because we cannot control the STDIN for this process.
In other words according to paragraph #1 spawning something like: "cmd.exe /c" or "cmd.exe /c cmd.exe" will not work.
However spawning something like "cmd.exe /c dir *.tmp" works fine.

#556 Updated by Marius Gligor almost 10 years ago

I did more researches on Windows OS regarding the console standard files redirection.
I have good news. Finally I succeeded to make cmd.exe interactive.
I found 2 issues on Windows:

- We should send \r\n at the end of the line instead \n like on Linux
- In windows we have receive no echo when we send a character to STDIN

Now I'm working to fix also this issue for Windows OS interactive process.

#557 Updated by Greg Shah almost 10 years ago

Code Review 0425a

1. I really like the new VT100AbstractRenderer. The only change needed is to put the private methods at the bottom of the class (below the protected ones) and move all public methods to the top.

2. The VT100SwingRenderer is nice and clean but it needs the protected methods moved to the bottom.

#558 Updated by Greg Shah almost 10 years ago

1. In Windows OS the P2J native implementation of interactive client spawn the OS command on a separate console widow. The executed command looks like: cmd.exe /c <command>
On this case is no need to call ThinClient.suspendedPause() because the user can see the results of the command execution on the interactive process window.

Whether this is OK or not depends upon how the 4GL works on Windows. I hope that the recently sent customer document provides enough information to access the Windows desktop via ICA. From there you can run the 4GL just like a Windows user would. Please note that there is both a GUI and ChUI (console) version of the 4GL on Windows. For this purpose, you must run the ChUI version. We must launch child processes just like that version. If it is in a separate window, then we may be fine. If it is in the same console, then I think we will need changes.

Using the same window for P2J client and interactive client is better to use ThinClient.suspendedPause() which allow the user to inspect the results after command execution before switch back to P2J screen.

Yes, the message and pause are required to get the compatible implementation.

Using the same window for P2J client and interactive client is better to use ThinClient.suspendedPause() which allow the user to inspect the results after command execution before switch back to P2J screen.

In Linux, Konsole is an X application (GUI) and on Windows notepad.exe is a WIN32 GUI app. We are only supporting ChUI/console apps as interactive child processes.

On AJAX clients only commands that runs on the interactive window like ls, dir, vi, nano, mc, ssh etc. could be used.

Yes, this is understood.

#559 Updated by Marius Gligor almost 10 years ago

A.) Today I succeeded to connect to windev01 via remote desktop connection.
I did some tests related to implementation for OS-COMMAND in Windows OS.
Here are my conclusions:

1. OS-COMMAND is always executed in Progress 4 GL for Windows OS by opening a separate console windows applications running the Windows shell cmd.exe

2. With default options SILENT=false NO-WAIT=false the cmd.exe is executed on interactive mode: cmd.exe /K <command>

3. On all other cases the cmd.exe is executed on non interactive mode: cmd.exe /C <command>

4. No message "Press space bar to continue." is displayed on all cases. This behaviour is available only on Linux.

B.) I changed my code according to this behaviour for Windows OS.

1. Swing clients the OS-COMMAND is always executed on a separate native console window in interactive / non interactive mode depending on options.

2. For web (AJAX) clients I tried to execute the cmd.exe inside the JS canvas like on Linux using native code by redirecting the STDIN, STDOUT and STDERR to anonymous pipes.
Unfortunately the cmd.exe on Windows cannot be executed properly on interactive mode because cmd.exe doesn't use STDIN for input.
When the STDIN is redirected to a pipe cmd.exe execute the command from pipe only after we write a line feed code (0x0a) on the pipe which is done when the ENTER key is pressed.
No echo characters are send during the write of other keystrokes on the pipe.
After the ENTER key is pressed the STDIN pipe is read and the command line is displayed on the screen and executed. If /Q (echo off) is used no echo is displayed.

I found this article http://support.microsoft.com/kb/q190351 from Microsoft which state:

Note Some console based applications do not use the standard handles for their input/output (IO) operations.
The Win32 API does not support redirection of these processes.

I tried many other ways to inject keystrokes inside cmd.exe console but with no success. I searched on the net for a possible solution but I didn't found any hint.
In conclusion at this moment the OS-COMMAND for AJAX clients in Windows works properly only for commands which use STDIN as input.
For applications using Console I/O like cmd.exe it works fine only in non interactive mode.

#560 Updated by Greg Shah almost 10 years ago

Code Review 0428a

1. In VT100SwingKeyboard and ChuiScreenDriver, please only query PlatformHelper.isUnderWindowsFamily() once (at construction) and save that value. It won't change during the lifetime of the instance.

2. In ChuiScreenDriver, is there a reason to explicitly import PlatformHelper?

3. command_line() in shell_win.c can overflow the cmd buffer (size MAX_COMMAND_LINE or 8192) if the contents of argv are big enough. This should be protected from overflow.

#561 Updated by Greg Shah almost 10 years ago

In conclusion at this moment the OS-COMMAND for AJAX clients in Windows works properly only for commands which use STDIN as input.
For applications using Console I/O like cmd.exe it works fine only in non interactive mode.

Is there any reason to continue searching?

If not, then make sure to document this limitation (in an obvious way) in the implementation classes. This should be in the javadoc AND in code comments where it makes sense (e.g. in VT100SwingKeyboard.handle() the code comments should explain WHY the keystrokes are "sent directly" and what "sent directly" really means).

#562 Updated by Marius Gligor almost 10 years ago

1. I found no way to make Windows shell cmd.exe to run properly in interactive mode for AJAX clients. It's works properly only in non interactive mode.
Only processes using STDIN, STDOUT and STDERR as input/output streams are working properly in interactive/ non interactive mode for AJAX clients in Windows.

2. Swing clients in Windows OS are working properly using a native shell window like Progress 4 GL for Windows.

#563 Updated by Marius Gligor almost 10 years ago

Just added some javadoc explanations.

#564 Updated by Greg Shah almost 10 years ago

Code Review 0429b

Everything is fine, except for this item from the previous code review:

1. In VT100SwingKeyboard and ChuiScreenDriver, please only query PlatformHelper.isUnderWindowsFamily() once (at construction) and save that value. It won't change during the lifetime of the instance.

#565 Updated by Marius Gligor almost 10 years ago

Fixed.

#566 Updated by Greg Shah almost 10 years ago

Code Review 0429c

Everything looks good. Please get it runtime regression tested.

#567 Updated by Marius Gligor almost 10 years ago

0429c - Interactive child process support ref. #2247
Passed regression tests. Committed revision 10517.

#568 Updated by Constantin Asofiei almost 10 years ago

Marius, I think there is a problem in ChuiScreenDriver.osCommand:460:

      if (isOsDefault && isWindowsOS)
      {
         // If defaults options and Windows OS use interactive mode.
         cmdList[1] = "/k";
      }

You are assuming that the received cmdList will always be of length 2... but this is not the case, a plain DOS statement will fail on Windows with an ArrayIndexOutOfBounds exception. The following fixes this:
      if (isOsDefault && isWindowsOS)
      {
         // If defaults options and Windows OS use interactive mode.
         cmdList = new String[] { cmdList[0], "/k" };
      }

I'll include this in an update with some other misc changes, if the fix looks OK to you.

#569 Updated by Constantin Asofiei almost 10 years ago

PS: something else I've forgot to mention: on Windows, using a program with only the DOS command, P2J waits for a key to be pressed before launching the terminal - which is not the case in 4GL.

#570 Updated by Marius Gligor almost 10 years ago

The cmdList is constructed on ProcessDaemon.prepareCommandLine(String[])
Indeed now I saw that if cmdList is empty the array is only 1 size.
You fix should be not enough because the cmdList could have a size greater than 2.
My idea was to transform a command like this:

cmd.exe /c &lt;other args&gt;

into

cmd.exe /k &lt;other args&gt;

So a fix should looks like:

      if (isOsDefault && isWindowsOS)
      {
         // If defaults options and Windows OS use interactive mode.
         if (cmdList != null && cmdList.length > 1 && "/c".equalsIgnoreCase(cmdList[1]))
         {
            cmdList[1] = "/k";
         }
      }

#571 Updated by Constantin Asofiei almost 10 years ago

Marius Gligor wrote:

So a fix should looks like:

OK, is more complex than I expected, so please fix it.

#572 Updated by Marius Gligor almost 10 years ago

Constantin Asofiei wrote:

PS: something else I've forgot to mention: on Windows, using a program with only the DOS command, P2J waits for a key to be pressed before launching the terminal - which is not the case in 4GL.

Yes I know about this. Seems to come from ThinClient.suspend() implementation not from my code.
The same behaviour is under Linux or Windows using native client which was implemented before.

#573 Updated by Constantin Asofiei almost 10 years ago

Marius Gligor wrote:

Constantin Asofiei wrote:

PS: something else I've forgot to mention: on Windows, using a program with only the DOS command, P2J waits for a key to be pressed before launching the terminal - which is not the case in 4GL.

Yes I know about this. Seems to come from ThinClient.suspend() implementation not from my code.
The same behaviour is under Linux or Windows using native client which was implemented before.

OK, I'm not sure if this is a regression of how 4GL 9.1c behaves.

Something else I've noticed: are the Backspace/Delete keys supposed to work in the VT100 implementation?

#574 Updated by Marius Gligor almost 10 years ago

Constantin Asofiei wrote:

Marius Gligor wrote:

Constantin Asofiei wrote:

PS: something else I've forgot to mention: on Windows, using a program with only the DOS command, P2J waits for a key to be pressed before launching the terminal - which is not the case in 4GL.

Yes I know about this. Seems to come from ThinClient.suspend() implementation not from my code.
The same behaviour is under Linux or Windows using native client which was implemented before.

OK, I'm not sure if this is a regression of how 4GL 9.1c behaves.

Something else I've noticed: are the Backspace/Delete keys supposed to work in the VT100 implementation?

Yes. I just tested on Linux using Swing client and it's works.
On the web client seems to be a small problem. I have to fix this on JS code.

For web clients on Windows OS is a limitation:

The Windows OS shell cmd.exe doesn't work properly in interactive mode.
Windows shell cmd.exe does not use STDIN as input nor STDOUT and STDERR as outputs.
Commands using STDIN, STDOUT and STDERR are working properly.

#575 Updated by Marius Gligor almost 10 years ago

This are some bugs fixes for interactive client:

- On Windows OS if OS-COMMAND has default values the command contains only one argument "cmd.exe"
Is no longer needed to look for /c and change to /k because only cmd.exe in the same as cmd.exe /k
This behaviour was already implemented in ProcessDaemon.prepareCommandLine(String[]).

- I fixed the DELETE key code for JS clients to send down the correct code.

- I fixed other issues related to screen cursor on JS and the ENTER key for Windows OS AJAX clients

#576 Updated by Constantin Asofiei almost 10 years ago

Marius Gligor wrote:

This are some bugs fixes for interactive client:

For some reason, when launching a process using the Swing client (on my Ubuntu, configured as standard dev workstation) the Backspace and Delete keys do not work...

#577 Updated by Marius Gligor almost 10 years ago

Have you tried a sh or a /bin/bash command?

#578 Updated by Constantin Asofiei almost 10 years ago

Marius Gligor wrote:

Have you tried a sh or a /bin/bash command?

Yes, they are working. This is how it looks when Backspace and Delete are pressed (in this order):

Also: is VT100 supposed to wrap output from i.e. echo $PATH?

#579 Updated by Marius Gligor almost 10 years ago

The wrap ON and OFF could be changed by keyboard sequences.
In class VT100AbstractRenderer the autoWrap was set to false by default.
I changed the default value to true and now it's works.

Regarding BACKSPACE and DELETE keys:

I tried the sh command on a Konsole application:
- The BACKSPACE works but DELETE not returns the same sequences. I have to do more investigations here.

When I executed a /bin/bash command both BACKSPACE and DELETE are working properly.

#580 Updated by Constantin Asofiei almost 10 years ago

Marius Gligor wrote:

Regarding BACKSPACE and DELETE keys:

I tried the sh command on a Konsole application:
- The BACKSPACE works but DELETE not returns the same sequences. I have to do more investigations here.

I think you are correct, only BACKSPACE is problematic.

When I executed a /bin/bash command both BACKSPACE and DELETE are working properly.

Yes, this works fine for me too.

#581 Updated by Marius Gligor almost 10 years ago

I searched on the Internet regarding BACKSPACE key:

1.The ^H is the normal response for VT100 terminal on response to a BACKSPACE key.

2. However I found that this could be changed by executing the following command:

$ stty erase ^H

Please try to execute this command from Swing interactive client at sh prompt.
Maybe a better solution is to send this command from our code prior to use the interactive client.

#582 Updated by Constantin Asofiei almost 10 years ago

Marius Gligor wrote:

Please try to execute this command from Swing interactive client at sh prompt.

Yes, now it works.

Maybe a better solution is to send this command from our code prior to use the interactive client.

I'm not sure if this is the right way. Can this be related to how the TERMINFO DB is configured?

#583 Updated by Marius Gligor almost 10 years ago

I did more tests on the customer's Windows server and I found that executing a command like:

OS-COMMAND "dir".

which means default values the dir command is executed and the shell window remain active.
On P2J a command like this is transformed in:
cmd.exe /c dir
but must be
cmd.exe /k dir

So I reverted the changes in ChuiScreenDriver and I did the appropriate changes.

#584 Updated by Greg Shah almost 10 years ago

Marius Gligor wrote:

Constantin Asofiei wrote:

PS: something else I've forgot to mention: on Windows, using a program with only the DOS command, P2J waits for a key to be pressed before launching the terminal - which is not the case in 4GL.

Yes I know about this. Seems to come from ThinClient.suspend() implementation not from my code.
The same behaviour is under Linux or Windows using native client which was implemented before.

Eugenie: I think this is in your Windows console support code. What are your thoughts?

#585 Updated by Greg Shah almost 10 years ago

Constantin Asofiei wrote:

Marius Gligor wrote:

Please try to execute this command from Swing interactive client at sh prompt.

Yes, now it works.

Maybe a better solution is to send this command from our code prior to use the interactive client.

I'm not sure if this is the right way. Can this be related to how the TERMINFO DB is configured?

Previously, we have had to make fixes to TERMINFO to get VT220, VT320 and xterm to work properly. This is documented in our books. Marius, please check if we need to make similar changes for VT100.

#586 Updated by Greg Shah almost 10 years ago

Code Review 0507c

I am fine with the changes so far. One note: the VT100AbstractRenderer has the variable named autoWarp instead of autoWrap. Although autoWarp is sounds cooler, I guess we should name it more descriptively. :)

Let's figure out these last two issues (Windows console pausing and BACKSPACE) and then we will get this in testing.

#587 Updated by Marius Gligor almost 10 years ago

I'm working to change the TERMINFO for vt100 as follow:

sudo cd /usr/share/terminfo/v/
infocmp vt100 > vt100.tmp

The content of vt100.tmp is:

#       Reconstructed via infocmp from file: /usr/share/terminfo/v/vt100
vt100|vt100-am|dec vt100 (w/advanced video),
        am, mc5i, msgr, xenl, xon,
        cols#80, it#8, lines#24, vt#3,
        acsc=``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~,
        bel=^G, blink=\E[5m$<2>, bold=\E[1m$<2>,
        clear=\E[H\E[J$<50>, cr=^M, csr=\E[%i%p1%d;%p2%dr,
        cub=\E[%p1%dD, cub1=^H, cud=\E[%p1%dB, cud1=^J,
        cuf=\E[%p1%dC, cuf1=\E[C$<2>,
        cup=\E[%i%p1%d;%p2%dH$<5>, cuu=\E[%p1%dA,
        cuu1=\E[A$<2>, ed=\E[J$<50>, el=\E[K$<3>, el1=\E[1K$<3>,
        enacs=\E(B\E)0, home=\E[H, ht=^I, hts=\EH, ind=^J, ka1=\EOq,
        ka3=\EOs, kb2=\EOr, kbs=^H, kc1=\EOp, kc3=\EOn, kcub1=\EOD,
        kcud1=\EOB, kcuf1=\EOC, kcuu1=\EOA, kent=\EOM, kf0=\EOy,
        kf1=\EOP, kf10=\EOx, kf2=\EOQ, kf3=\EOR, kf4=\EOS, kf5=\EOt,
        kf6=\EOu, kf7=\EOv, kf8=\EOl, kf9=\EOw, lf1=pf1, lf2=pf2,
        lf3=pf3, lf4=pf4, mc0=\E[0i, mc4=\E[4i, mc5=\E[5i, rc=\E8,
        rev=\E[7m$<2>, ri=\EM$<5>, rmacs=^O, rmam=\E[?7l,
        rmkx=\E[?1l\E>, rmso=\E[m$<2>, rmul=\E[m$<2>,
        rs2=\E>\E[?3l\E[?4l\E[?5l\E[?7h\E[?8h, sc=\E7,
        sgr=\E[0%?%p1%p6%|%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;m%?%p9%t\016%e\017%;$<2>,
        sgr0=\E[m\017$<2>, smacs=^N, smam=\E[?7h, smkx=\E[?1h\E=,
        smso=\E[7m$<2>, smul=\E[4m$<2>, tbc=\E[3g,

I changed the kbs=^H trying the following values one by one kbs=\177, kbs=\b, kbs=\011 and kbs=\EOD (kcub1)
Looking inside my emulator the more appropriate values are \011 and \EOD if this represents the sequences which are send as a response to BACKSPACE key.
Also I did the same changes for cubl and I recompiled the database

tic vt100.tmp

I tested using infocmp vt100 > vt100.tmp and the changes was made.
However no changes on console always the same ^H on BACKSPACE.
Do you have any idea how to fix that?

#588 Updated by Greg Shah almost 10 years ago

I'm not sure I have too much guidance here. The only thing I see is that I think the ASCII BS is 0x08 or octal \010. Isn't octal \011 the 0x09 or TAB key?

There are a couple of documents that are potentially useful:

http://www.ibb.net/~anne/keyboard.html
http://www.ibb.net/~anne/keyboard/keyboardprev.html (this is the one that more closely maps to what we want, see the terminfo section)

#589 Updated by Eugenie Lyzenko almost 10 years ago

Greg Shah wrote:

Marius Gligor wrote:

Constantin Asofiei wrote:

PS: something else I've forgot to mention: on Windows, using a program with only the DOS command, P2J waits for a key to be pressed before launching the terminal - which is not the case in 4GL.

Yes I know about this. Seems to come from ThinClient.suspend() implementation not from my code.
The same behaviour is under Linux or Windows using native client which was implemented before.

Eugenie: I think this is in your Windows console support code. What are your thoughts?

What I did special regarding to Windows console implementation and pause is the eliminating additional pause for Windows P2J but AFTER command is executing, not BEFORE. And this is not in platform dependent code but in ProcessDaemon.launch(). The terminal suspend() was not changed/adjusted.

#590 Updated by Eugenie Lyzenko almost 10 years ago

I changed the kbs=^H trying the following values one by one kbs=\177, kbs=\b, kbs=\011 and kbs=\EOD (kcub1)

Try ^? and may be "\e[3~".

#591 Updated by Marius Gligor almost 10 years ago

Changing TERMINFO seems to not work on all cases see 5.12 on this document
http://www.tldp.org/HOWTO/Keyboard-and-Console-HOWTO-5.html
However on 5.13 he claim to offer a complete solution.

#592 Updated by Greg Shah almost 10 years ago

Does this solution documented in section 5.13 solve the problem with sh?

#593 Updated by Marius Gligor almost 10 years ago

Greg Shah wrote:

Does this solution documented in section 5.13 solve the problem with sh?

I didn't try but I found a more clever solution. I fixed the issue directly on the VT100 emulator
by changes the code for BACKSPACE to 127 (0x7f).
Basically is the same idea to use code 127 for BACKSPACE but implemented directly into the software emulator
without any other changes on system.

#594 Updated by Greg Shah almost 10 years ago

Perfect! Yes, that is clever.

#595 Updated by Marius Gligor almost 10 years ago

1. Fixed the BACKSPACE key on VT100 emulator (VT100AbstractKeyboard).
2. Fixed waiting issue in TypeAhead.suspend()

#596 Updated by Marius Gligor almost 10 years ago

Add also the fix for BGCOLOR and FGCOLOR in BaseEntity to use int values on setters.

#597 Updated by Constantin Asofiei almost 10 years ago

Greg/Marius, something to keep in mind about shell launch on Windows: when using a web client and the clients launches a shell (via DOS), then the shell (cmd.exe) will not be visible/accessible from the web client. I'll add this as a limitation to the docs.

#598 Updated by Marius Gligor almost 10 years ago

Constantin Asofiei wrote:

Greg/Marius, something to keep in mind about shell launch on Windows: when using a web client and the clients launches a shell (via DOS), then the shell (cmd.exe) will not be visible/accessible from the web client. I'll add this as a limitation to the docs.

Yes this is a more general issue using interactive processes on web clients see my remarks on note #555

#599 Updated by Constantin Asofiei almost 10 years ago

Marius Gligor wrote:

Yes this is a more general issue using interactive processes on web clients see my remarks on note #555

Marius, thanks.

Something else about these notes in note 490:

f. the request seen by server C has the cookie set (if and only if the request was the redirected GET from the original browser) and the authorization passes

The part I am really unclear about is step (e). It seems to me that the cross-origin nature of the cookie should break this step.

You are correct, the cross-origin nature of the cookie breaks this step; if the initial login page is on localhost and the redirect is on i.e. 192.168.1.2, then the redirect will not see the cookie and will not authorize the client. I guess this is OK for now (as the client and server are on the same machine), but we will need to enhance this, once the P2J Server will be allowed to start P2J Clients on other machines.

#600 Updated by Greg Shah almost 10 years ago

Code Review 0508b

My only concern with this update is the change to TypeAhead. Have you confirmed that all ScreenDriver instances will return -1 when readKey() is interrupted? This is a very sensitive area of the code.

#601 Updated by Marius Gligor almost 10 years ago

I tried to do as much as minimum changes in the code due to it's complexity in terms of thread synchronization.
The code inside TypeAhead isn't my work but what I understood is the follow:

- When the keyboard reader thread is suspended the suspended flag notify the thread about the request and goes to wait.
- The KeyReader thread when check the suspended flag and found a true value execute a notify and than goes to wait.
- On the initial implementation seems that both thread goes to wait and a deadlock occur.
- Fortunately a keystroke seems to wakeup the KeyReader which in term notify the caller thread which leave the wait state.

According to my tests the keyReader thread is blocked because it uses a LinkedBlockingQueue.take when get the keystroke from key buffer.
Please look in TypeAhead line 454 int key = OutputManager.instance().readKey();
LinkedBlockingQueue.take is a blocked operation. When is interrupted -1 is retuned.
The code in suspend() is safe because after interrupting the key reader we wait to be notified when reader goes to wait.
When the suspend() exit key reader is suspended

I did tests and I found to works fine with all screen drivers on both Windows and Linux.

#602 Updated by Marius Gligor almost 10 years ago

Another very important aspect is the code in line 443 which force recheck for suspension in order to avoid a spurious wake-up.
Even if we interrupt the thread from suspend() the key reader recheck the suspension and goes back to sleep and notify us about this.

#603 Updated by Greg Shah almost 10 years ago

OK, go ahead with runtime regression testing for this update.

#604 Updated by Marius Gligor almost 10 years ago

0508b - Passed regression tests. Committed revision 10525.

#605 Updated by Marius Gligor almost 10 years ago

Here is a first solution for issue #2306 implements copy/paste for web client

1. The system clipboard cannot be accessed in JS for security reasons. Only IE on Windows allow to access the system clipboard.
As a consequence I designed a simple text clipboard in JS code.

2. For selection menu I used Dojo toolkit the latest release 1.10.0 (I tested also the previous 1.9.2 release)
I designed a JS pop-up context menu having 3 options Select, Copy and Paste.

3. For selection drawing I'm using a layered canvases technique. Basically we have two canvases the main canvas and a transparent canvas.
The two canvases are overlapped. When the selection is draw the transparent canvas is used and It is no need to redraw (refresh) the main canvas.
Also we have two types for graphical selection. A rectangle (stroke) like on the Swing P2J or a fill semi-transparent rectangle.
The selection type is configurable (stroke parameter) and by default is like on the Swing P2J.

4. Doing a lot of tests I observed that often when I reloaded the main html page or even on a first load on Linux the Dojo toolkit throws some JS errors.
This is a strange behaviour and on the next step I have to find a solution to fix this issue.

#606 Updated by Constantin Asofiei almost 10 years ago

Marius Gligor wrote:

2. For selection menu I used Dojo toolkit the latest release 1.10.0 (I tested also the previous 1.9.2 release)

Is there supposed to be a dojo-release-1.10.0.zip file in the P2J project? Because I can't find it...

#607 Updated by Marius Gligor almost 10 years ago

1. I found and I fixed the issue related to Dojo modules loading. The problem was a race condition in DojoToolkitHandler jetty handler.
The method deliverDojoModule MUST be synchronized. I did new tests and now it works properly.

2. The Dojo toolkit 1.9.2 is available from: ~/shared/projects/p2j/dojo/ location
Today I copied also the latest Dojo 1.10.0 toolkit on the same location.
The manifest was changed some times ago to put on CLASSPATH the Dojo toolkit 1.9.2 and now I changed to 1.10.0

#608 Updated by Greg Shah almost 10 years ago

I haven't gotten the chance to do a code review yet. But I think we need to be using the new HTML5 clipboard API (as long as it can do the job):

http://caniuse.com/clipboard
http://www.w3.org/TR/clipboard-apis/ (new API that is hopefully functional enough to do the job and which is mostly supported in recent browsers)
http://brooknovak.wordpress.com/2009/07/28/accessing-the-system-clipboard-with-javascript/ (nice summary of the "old way" to work around clipboard restrictions)

#609 Updated by Marius Gligor almost 10 years ago

- The HTML 5 Clipboard API is currently only a draft and the specifications aren't implemented yet by any web browser.

- I found some examples for paste from system clipboard but these examples allow only to register a listener for paste command triggered from browser context menu or CTRL-V keyboard.
The implementation is different in Firefox and Chrome. I found no examples for copy to system clipboard.

- Another solution which I've not yet tested is to use a JQuery plug-in based on a zeroclipboard JS library from GitHub.
The solution is based on the injection of a hidden Adobe flush plug-in and use this object to copy and paste to system clipboard.
The solution have a lot of serious limitations and is subject to security threats (see https://github.com/zeroclipboard/zeroclipboard)
Also the keyboard strokes (CTRL-C and CTRL-V) cannot be used on our case to trigger copy and paste commands.

- The only suitable solution available today for our case is the IE implementation which allow direct access to system clipboard to read and write.

#610 Updated by Constantin Asofiei almost 10 years ago

Marius Gligor wrote:

- I found some examples for paste from system clipboard but these examples allow only to register a listener for paste command triggered from browser context menu or CTRL-V keyboard.

If think we should allow the user to paste text (via CTRL-V); can/should this be done by reading the clipboard content and sending it as a key, char-by-char? Can you check how the console client behaves if i.e. CTRL-V/Shift-Insert is pressed?

- Another solution which I've not yet tested is to use a JQuery plug-in based on a zeroclipboard JS library from GitHub.

I don't think the user will have much use of the copy-paste feature, if pasting the text selection from the ChUI (or GUI) client outside of the browser window is not allowed. More, copy-paste will be much more expected when the GUI ajax client will be in use (just think of all the fill-in's and text editors to which the user can select text and copy/paste it as necessary).

#611 Updated by Marius Gligor almost 10 years ago

  • File paste_example.html added

I tested CTRL-V/Shift-Insert strokes on both P4GL and P2J.
Shift-Insert switch the insert mode on P4GL editor.
CTRL-V seems to have no effects and could be used for paste operations.

In the mean time I created a short example (see paste_example.html) that could be used to paste from system clipboard.
I designed the JS code to work on all major browsers. I tested with IE, Firefox, Chrome and Opera and on all it works!

The limitation of this solution is that it works only from keyboard by pressing the CTRL-V key.
We could try to integrate the code from this example in our web client JS code.

#612 Updated by Marius Gligor almost 10 years ago

  • File deleted (paste_example.html)

#613 Updated by Marius Gligor almost 10 years ago

#614 Updated by Greg Shah almost 10 years ago

Code Review 0703a

Overall the code looks good.

1. The sendResponse() and detectContentType() in DojoToolkitHandler seem generic enough that they would be useful in the parent class.

2. Constantin is correct: we can't implement clipboard support just within our own implementation. The key requirement is for integration with the system clipboard. Your paste example looks good. I am fine with the limitation that SHIFT-INS does not work. CTRL-V and the browser's paste menu item is enough, though if possible I would like to have it linked to our Paste popup menu too.

3. I assume you can make a similar example for copy (as you did for paste). For copy, the tricky part is that we 4GL use CTRL-C for generating the STOP condition. However, I think we can let CTRL-C be used a copy in the case (and only in that case) where the user has made an explicit selection. When this copy occurs, we clear the selection and CTRL-C will be given to the "terminal" again. I also expect the browser's copy menu should work fine. Again, if possible please link this to our popup menu.

#615 Updated by Marius Gligor almost 10 years ago

1. Unfortunately we cannot link CTRL-V to our pop-up menu.
For security reasons the event listener for paste is trigerred only when the CTRL-V is pressed from browser, taht is, only under browser control.
We cannot force a paste operation from JS code using

document.execCommand('paste', ...)
. This works today only on IE which fully support JS clipboard API.
All we could do is to copy from system clipboard to our application clipboard when CTRL-V is pressed and in the same time to send down to server the pasted text.

2. The CTRL-C is even more restrictive. When CTRL-C is pressed the selected text from the current html element having the focus is copied into system clipboard.
The html element must be an editable element (input) and he must be the focus owner at the moment when CTRL-C is pressed.
Our selection on canvas is not a selection in terms of system clipboard and we cannot specify which data should be copied to clipboard.
Again the

document.execCommand('copy', ...)
solution is available only on IE.

#616 Updated by Constantin Asofiei almost 10 years ago

Marius Gligor wrote:

2. The CTRL-C is even more restrictive. When CTRL-C is pressed the selected text from the current html element having the focus is copied into system clipboard.

Is it possible to use an editable input/textarea element this way: after the user selects the text, the selected text is set as the content for the input/textarea, via js (and everything is selected); this element is set as focus, and if the user presses CTRL-C, should pick the text up. Not sure what happens if the element is hidden.

#617 Updated by Marius Gligor almost 10 years ago

I have to try. It is the best way to find out if it works or not.

#618 Updated by Constantin Asofiei almost 10 years ago

Another solution would be this: for copying, the user selects the text, then right-clicks and chooses from the menu the Copy option; once this option is chosen, a dialog (maybe modal?) is displayed to the user, with the selected text (which is already highlighted/selected). This dialog will inform the user to press CTRL-C to copy it. If CTRL-C is pressed directly (after the selection is completed and before Copy option is chosen), is it possible to add an event which does what the Copy menu does (i.e. the intermediate dialog)?

For pasting, I think your approach to "copy from system clipboard to our application clipboard when CTRL-V is pressed and in the same time to send down to server the pasted text." should be OK - no intermediate dialog is needed.

#619 Updated by Marius Gligor almost 10 years ago

Here is a solution which works with CTRL-C and CTRL-V by hidding the input element using CSS.
If the input is of type="hidden" id doesn't work because he cannot gain the focus.
When the user press CTRL-C we have to copy the data to the input element set focus and select text.

The soulution with dialogs to copy and paste is the simple way to implements copy/paste and can be linked with our pop-up menu.

function copyToClipboard(text) {
  window.prompt("Copy to clipboard: Ctrl+C, Enter", text);
}

function pasteFromClipboard() {
  return window.prompt("Paste from clipboard: Ctrl+V, Enter", "");
}

but this solution is not so elegant because the user select copy from context menu and after that he should press also CTRL-C.

#620 Updated by Marius Gligor almost 10 years ago

Here is a simple soultion using dialogs.

#621 Updated by Marius Gligor almost 10 years ago

1. The sendResponse() and detectContentType() in DojoToolkitHandler seem generic enough that they would be useful in the parent class.

The parrent class of DojoToolkitHandler is AbstractHandler which is part of the jetty library.
We cannot move sendResponse() and detectContentType() to AbstractHandler.

#622 Updated by Greg Shah almost 10 years ago

I don't want to use the dialog approach unless absolutely needed. At a minimum, the paste can be done properly (without dialogs), right?

In regard to the copy, please investigate if we can make a non-hidden focused input element that is not visible (using sizing/positioning, opacity or the visible attribute). That is much cleaner than the dialog approach.

Finally, if we can drive all of this using CTRL-C and CTRL-V, then it seems there is no reason for a popup menu. The selection can be driven purely off mouse events, right?

#623 Updated by Marius Gligor almost 10 years ago

1. The CTRL-C example attached on #619 is exactly what you are looking for. The input is made not visible using CSS style.

2. Yes we could do the selection using only the mouse events and use CTRL-C and CTRL-V for copy and paste.
In this case the Dojo toolkit is no longer needed. However the changes DojoToolkitHandler should be kept because has been tested.

3. May I do changes to use only the mouse events for selection and CTRL_C/CTRL-V for copy/paste?

#624 Updated by Greg Shah almost 10 years ago

1. The sendResponse() and detectContentType() in DojoToolkitHandler seem generic enough that they would be useful in the parent class.

The parrent class of DojoToolkitHandler is AbstractHandler which is part of the jetty library.
We cannot move sendResponse() and detectContentType() to AbstractHandler.

Right. But we can create a new base class (extends AbstractHandler) that has those features and which can be used from DojoToolkitHandler and other subclasses.

1. The CTRL-C example attached on #619 is exactly what you are looking for. The input is made not visible using CSS style.

If you can make both CTRL-C and CTRL-V work for accessing the system clipboard, then that is the best solution.

Is there any problem with event handling during the time when the input element has focus?

Also, when there is no current selection, the CTRL-C needs to still be forwarded to the server for use by the 4GL. Only when there is a selection, should CTRL-C be honored as a copy.

In this case the Dojo toolkit is no longer needed. However the changes DojoToolkitHandler should be kept because has been tested.

Agreed.

3. May I do changes to use only the mouse events for selection and CTRL_C/CTRL-V for copy/paste?

Yes. You should also allow the user to cancel the current selection using ESC. This would only work while a selection was active. Once the selection is cleared, ESC would be forwarded to the 4GL key processing again.

#625 Updated by Marius Gligor almost 10 years ago

I will try to do the appropriate changes to implements the copy/paste with CTRL-C and CTRL-V, ESC to clear selection and mouse events to draw selection.
Regarding DojoToolkitHandler I have to create another abstract class let say AbstractHandlerCommons which extends AbstractHandler and move the generic methods as you suggested.

#626 Updated by Marius Gligor almost 10 years ago

Here is the implementation of copy/paste using:

- Mouse drag to select.
- ESC key to clear selection.
- CTRL-C key to copy only when have a selection.
- CTRL-V key to paste always.

Tested with Chrome, Firefox, IE and Opera.

#627 Updated by Greg Shah almost 10 years ago

Code Review 0709a

The code looks very good.

1. In onkeyup(), shouldn't we still call preventDefault() and stopImmediatePropagation() when the event is not CTRL-C or CTRL-V?

2. CTRL-C still gets passed to the server when there is no selection. My only concern is about how we handle CTRL-V. If I understand correctly, CTRL-V is never passed to the server. This means this is another key combination that is excluded from 4GL app compatibility. That may be OK if we can't think of any way around this, but I wanted to discuss and understand it.

#628 Updated by Marius Gligor almost 10 years ago

1. The onkeyup() is the last event fired on key events. If we prevent default action and stop propagation we could be sure that no other actions are performed by browser.

2. Regarding CTRL-V unfortunately we have no other conditions to use like on CTRL-C.
Supposing that CRTL-V is used by a P4GL application this could be a problem if we send CTRL-V to application and in the same time we perform a copy operation.
Probabbly the user expect something else to happen on CTRL-V.

#629 Updated by Greg Shah almost 10 years ago

The onkeyup() is the last event fired on key events. If we prevent default action and stop propagation we could be sure that no other actions are performed by browser.

Yes, let's do this for all keystrokes that are not CTRL-C or CTRL-V.

Regarding CTRL-V unfortunately we have no other conditions to use like on CTRL-C.

The only thing I can think of is when the text read from the clipboard is the empty string. What do you think?

Supposing that CRTL-V is used by a P4GL application this could be a problem if we send CTRL-V to application and in the same time we perform a copy operation.
Probabbly the user expect something else to happen on CTRL-V.

Other than the empty string case, we will just have to live with this restriction.

#630 Updated by Marius Gligor almost 10 years ago

Yes this might be a solution. At the moment the CTRL-V is pressed we cannot determine if clipboard is empty but we could find later.
In the paste events handler if clipboard is not empty we could send the CTRL-V. I have to check also if paste event is fired when the clipboard is empty on all browsers.

#631 Updated by Constantin Asofiei almost 10 years ago

Do you think is OK to have some browser toggle box to set/change how CTRL-V is used? This toggle box could be defaulted to some chuiWeb setting; its set/unset modes would determine what happens when the user presses CTRL-V.

#632 Updated by Marius Gligor almost 10 years ago

Here are the changes which I made to implements CTRL-V rule.

#633 Updated by Marius Gligor almost 10 years ago

By the way one issue that should be also discussed. When a paste operation is made the text is not discarded from clipboard.
So in order to send CTRL-V to P4GL application the clipboard MUST be empty. The user must clear the clipboard from outside id he need to use CTRL-V in P4G application.
We could clear the clipboard from JS when CTRL-V is handled but this is not a standard mode to work with clipboard.

#634 Updated by Greg Shah almost 10 years ago

Constantin Asofiei wrote:

Do you think is OK to have some browser toggle box to set/change how CTRL-V is used? This toggle box could be defaulted to some chuiWeb setting; its set/unset modes would determine what happens when the user presses CTRL-V.

I think this is an excellent idea. We can default its state from server-side (directory) configuration, if present. If the directory does not have any configuration, then for now we can leave clipboard support defaulted to ON.

To keep the interface clean, let's add the Dojo popup menu back with just this single checkbox item: "Enable Clipboard".

#635 Updated by Greg Shah almost 10 years ago

We could clear the clipboard from JS when CTRL-V is handled but this is not a standard mode to work with clipboard.

Good point.

Since it is not the standard way users expect to work with the clipboard, we won't clear the clipboard at this time.

#636 Updated by Marius Gligor almost 10 years ago

"Enable Clipboard" means also CTRL-C? Basically means the entire clipboard?

#637 Updated by Greg Shah almost 10 years ago

Code Review 0711a

It looks good. Please implement the "Enable Clipboard" feature and then this task will be done.

#638 Updated by Greg Shah almost 10 years ago

Marius Gligor wrote:

"Enable Clipboard" means also CTRL-C? Basically means the entire clipboard?

Yes, exactly. When the "Enable Clipboard" is OFF, the client should work exactly as before. That means the mouse selection processing should be disabled too and all event processing should be restored (allowed to flow to the server).

#639 Updated by Marius Gligor almost 10 years ago

Another question please. If in directory the clipboard is set to OFF (disabled) should we hide also the pop-up menu?

#640 Updated by Constantin Asofiei almost 10 years ago

Marius Gligor wrote:

Another question please. If in directory the clipboard is set to OFF (disabled) should we hide also the pop-up menu?

The directory setting just tells the web client how the clipboard is by default (ON or OFF). Regardless of what the directory setting is, the user needs to be allowed to disable/enable it as needed.

#641 Updated by Marius Gligor almost 10 years ago

Enable/Disable clipboard. Add node "clipboard" to the "container" node "chuiWeb":

  <node class="container" name="chuiWeb">
    ....
    <node class="boolean" name="clipboard">
       <node-attribute name="value" value="FALSE"/>
    </node>     

If missing the default value is TRUE clipboard enabled.
The user may enable/disable the clipboard behaviour using a context pop-up menu created using Dojo toolkit having a single checked item "Enable Clipboard".

#642 Updated by Greg Shah almost 10 years ago

Code Review 0714a

The update looks really good.

I realize that the escape key selection processing in the onkeydown() function (tty.keyboard.js) is safe right now because tty.clipboard.haveSelection() is protected. But I would like the selection clearing to be wrapped in if (tty.clipboard.enabled) anyway for 2 reasons:

  1. Future changes to tty.clipboard.haveSelection() won't cause regressions/surprises.
  2. Future readers/maintainers of tty.keyboard.js will be able to see immediately that it is safe instead of having to look at tty.clipboard.haveSelection() first.

Other than that I think this is good to go. I don't think this needs regression testing. Please make the above change, post it here and then you can check in/distribute the update.

#643 Updated by Constantin Asofiei almost 10 years ago

Greg/Marius: is there a specific reason why the dojo zip is not included in the p2j bzr repo? More, how will the web client act if dojo is not in the classpath (and the JS relies on dojo)?

#644 Updated by Marius Gligor almost 10 years ago

Dojo toolkit is big in size (~13 Mb). So far the dojo toolkit was not used in P2J project. However it was added to the classpath some time ago (see the manifest file)
I updated the classpath inside the manifest file to use the latest Dojo release (1.10). I will commit the Dojo zip at the end of this task and I will inform the team that Dojo zip
is missing in the distributed archive. They could update the dojo zip either from bzr or from ~/shared/projects/p2j/dojo/

#645 Updated by Greg Shah almost 10 years ago

OK, good plan.

#646 Updated by Marius Gligor almost 10 years ago

Here are the requested changes.

#647 Updated by Greg Shah almost 10 years ago

It looks good. Please note that we are backing out 10568, but that is not yet complete. Until that happens, please hold off on your check-in and distribution.

#648 Updated by Marius Gligor almost 10 years ago

0714b.zip committed revision 10570.

#649 Updated by Constantin Asofiei over 9 years ago

Fixes a starvation problem while reading keys via the web client driver. Will runtime test it.

#650 Updated by Constantin Asofiei over 9 years ago

Constantin Asofiei wrote:

Fixes a starvation problem while reading keys via the web client driver. Will runtime test it.

No issues related to this during testing, released to rev 10678./

#651 Updated by Greg Shah about 9 years ago

The following are the next development steps to implement the GUI web client:

  1. Much of the web-specific code for the ChUI client is not specific to ChUI. But the initial implementation was not designed to maximize reuse. Instead, this common code was embedded inside the ChUI-specific web client. This code needs to be refactored to move into common/generic web support classes so that the GUI web client can share that code.
  2. The ChUI web client was implemented primarily by extending the ChUI Swing client. Some ChUI functionality was pushed into base classes and then reused via inheritance. One problem is that those base classes themselves inherit from Swing components (e.g. JComponent) and the implementation is very Swing-dependent. This makes it very hard to understand which code is actually needed for the web client. This Swing-dependency needs to be eliminated from the ChUI web client. This means moving away from the ChuiSimulator approach.
  3. Rework the client side javascript code to maximize the amount of generic/shared code. This also means implementing multiple locations for loading of javascript code on the server and the ability to specialize or differentially load javascript that is specific to a given UI-modality (ChUI vs GUI).
  4. There is no common parent hierarchy for the 5 existing drivers: native NCURSES ChUI, batch driver ChUI, Swing ChUI, Web ChUI, Swing GUI. We added drivers over time, organically. When adding them, we took some short cuts and never tried to get a clean and understandable parent hierarchy. Instead, there is a great deal of duplication, inconsistencies and it is harder to understand the drivers than necessary. By eliminating the Swing dependent parent classes in the remaining 2 Swing clients, a single, clean hierarchy can be implemented.
  5. Direct usage of AWT (and some Swing) features in the GUI code must be limited to inside the driver itself. Currently, there is quite a bit of such usage from GUI code that is expected to be common (e.g. GuiDriver, GuiPrimitives, PaintStructure, *GuiImpl widget classes). Access to Font, Image, Stroke, Color and other similar AWT features needs to be abstracted behind the driver interface.
  6. There is code in the Swing GUI driver that is generic enough that it should be shared for the web GUI as well. This means that there is some refactoring to move this common code into generic GUI support classes.
  7. Using the massive refactoring in the prior steps, a server-side web GUI driver must be built. Initially, the one area that can be left undone is the extension of the websockets protocol to the GUI driver primitives.
  8. Extend the websockets protocol to support the full set of GUI driver primitives.
  9. Implement the client side GUI-specific driver portions.

There will be more testing and refinement from there. For example, we will need to make sure that we test the implementation for compatibility with the Swing client AND for cross-browser compatibility. But the above steps will get us to a good foundation.

I have finished a first cut at steps 1 - 4 and am currently working on step 5. This is a massive refactoring update and I have not gotten everything currently edited into a working state yet. When it is at least mostly working I will upload an update for review. I intend to get through step 7 and then make sure that everything gets tested and checked in. From there Anton and I will make the final push for steps 8 and 9.

#652 Updated by Greg Shah about 9 years ago

Research Item

What is the best approach to provide web GUI client support for images as provided in the 4GL?

The 4GL GUI is implemented using Windows and the native WIN32 API. From 4GL syntax, it provides the ability to use images on GUI buttons and it also provides an image widget which allows a specific image to be placed directly in a frame. In addition to this, there are some ways that a 4GL GUI application can implicitly use images. For example, the min/max/restore buttons in each window, the system menu icon (top left corner of the window) and the iconified (minimized) representation of a window as an icon. These are not directly controlled from the 4GL, but we must support the implicit use cases as well as the explicit use cases.

The current design for a GUI driver has a single method defined (drawImage()) to support images. All other loading, sizing, positioning, transformations and other processing is done in common code. But to do this there are multiple dependencies on non-generic classes:

  • java.awt.Image
  • java.awt.image.*
  • javax.imageio.*
  • javax.imageio.stream.*
  • some image types access via javax.imageio require the lib/imageio-*.jar files to be present at runtime

More details can be seen in #2446 (where most image work was done, including both buttons and image widgets) and #2252 (master GUI support task - note 681 is especially useful).

Some use of these is going to be required, even in the generic code, because we must be able to read in image formats that are native to Windows (or at least commonly used in Windows) like .bmp or .ico.

I plan to abstract the core image processing to eliminate direct use of the java.awt.Image class in the generic code. But the plan is not complete and it will depend to some degree on how we intend to push this support out to the browser side.

Some Windows-native image types will need to be translated into forms that can be used in the browser. That translation should certainly happen in Java code.

The biggest question in my mind is how exactly will we take the images from the server and deliver them to the client such that they are loaded as javascript Image objects. I see the following as options:

  • The most common way to do this is a hidden <img> tag and setting the src attribute to a server-side URLs. Our current approach is that the client is a single page web application, loading data/content via URL will require additional infrastructure and some care to handle all security concerns. Besides the additional security issues, this would also represent a deviation from our simple bidirectional websockets approach, meaning that it makes the overall approach harder to understand because it has more "moving parts". I do NOT prefer this approach.
  • We can send the image data as a byte[] using the web socket. This is simple, clean and has not additional security implications. How then would we turn this array of bytes into something usable (optimally to an in-memory Image javascript object)?
    • The ImageData support in canvas could be used (getImageData()/putImageData()) to access the raw pixels. Doing this may have positioning issues and we would also have to handle any alpha support manually. We could also use canvas.toDataURL() to get a data: URI from it, which can be turned into an Image object easily.
    • We could generate a data: URI manually (see https://developer.mozilla.org/en-US/docs/Web/HTTP/data_URIs). This would either require us to implement base-64 encoding in javascript (will atob() work?) or send the bytes down already encoded (not a good idea because this expands the data sent).

One concern I have about data: URI support is whether there are size limits to the images that can be loaded in this manner.

Anton: please do some further analysis and report your findings in this task. Ask any questions here as needed.

#653 Updated by Anton Breaur almost 9 years ago

We can send the image data as a byte[] using the web socket. This is simple, clean and has not additional security implications. How then would we turn this array of bytes into something usable (optimally to an in-memory Image javascript object)?

Agree, this looks like a better approach because of the existing websocket communication. Once we have the image as a byte array in javascript, we can easily convert into base64 using the window.btoa(byteData) function.
From here, creating an Image object means instantiating one and then setting the src attribute as such:
image.src = "data:image/png;base64," + base64
Then, having a canvas context the image can be drawn like this: ctx.drawImage(image, 0, 0)

The ImageData support in canvas could be used (getImageData()/putImageData()) to access the raw pixels. Doing this may have positioning issues and we would also have to handle any alpha support manually. We could also use canvas.toDataURL() to get a data: URI from it, which can be turned into an Image object easily.

We can create an Image object as described earlier, without involving a canvas context, other than the one to actually paint the image.
Not sure why we need to access raw pixels, but we can display the Image cropped or scaled as desired. Also, the alpha will probably need to be handled manually; the canvas context alpha can be updated and restored as such:
ctx.save();
ctx.globalAlpha = 0.4;
ctx.drawImage(img, x, y);
ctx.restore();

We could generate a data: URI manually (see https://developer.mozilla.org/en-US/docs/Web/HTTP/data_URIs). This would either require us to implement base-64 encoding in javascript (will atob() work?) or send the bytes down already encoded (not a good idea because this expands the data sent).

Manually generating data URI is the approach I am in favour of using. The base64 encoding in javascript works as described earlier, using btoa() function. Agree, the base64 encoded image is 1/3 larger than the original, so it is better to send byte data and encode in javascript.

One concern I have about data: URI support is whether there are size limits to the images that can be loaded in this manner.

I cannot find any other limit than an IE8 limit of 32kb for the URI, which means less than 24kb for the original image, but I suppose IE8 is out of our scope.

#654 Updated by Greg Shah almost 9 years ago

Following up on my note 651, the attached update is the first (partially working) pass at steps 1 - 7.

I have also attached a compare.sh helper to make code reviews easier (unzip the update and then run ./compare.sh <p2j_dir_with_unchanged_bzr_10848> <dir_where_update_was_unzipped>).

The following is a summary from bzr of the changes:

removed:
  src/com/goldencode/p2j/ui/client/chui/driver/ChuiScreenDriver.java
  src/com/goldencode/p2j/ui/client/driver/swing/GenericScreenDriver.java
  src/com/goldencode/p2j/ui/client/gui/driver/GuiScreenDriver.java
  src/com/goldencode/p2j/ui/client/swing/
added:
  src/com/goldencode/p2j/ui/client/FontDetails.java
  src/com/goldencode/p2j/ui/client/FontMetricsHelper.java
  src/com/goldencode/p2j/ui/client/FontStyle.java
  src/com/goldencode/p2j/ui/client/chui/driver/AbstractChildProcess.java
  src/com/goldencode/p2j/ui/client/chui/driver/AbstractChuiDriver.java
  src/com/goldencode/p2j/ui/client/chui/driver/EmulatedTerminalState.java
  src/com/goldencode/p2j/ui/client/chui/driver/VT100EmulatorChildProcess.java
  src/com/goldencode/p2j/ui/client/chui/driver/VT100ProgressKeyboard.java
  src/com/goldencode/p2j/ui/client/chui/driver/console/NativeChildProcess.java
  src/com/goldencode/p2j/ui/client/chui/driver/web/res/p2j.clipboard_helpers.js
  src/com/goldencode/p2j/ui/client/driver/AbstractDriver.java
  src/com/goldencode/p2j/ui/client/driver/web/
  src/com/goldencode/p2j/ui/client/driver/web/ClientProtocolHooks.java
  src/com/goldencode/p2j/ui/client/driver/web/EmbeddedWebServerImpl.java
  src/com/goldencode/p2j/ui/client/driver/web/WebClientMessageTypes.java
  src/com/goldencode/p2j/ui/client/driver/web/WebClientProtocol.java
  src/com/goldencode/p2j/ui/client/driver/web/WebPageHandler.java
  src/com/goldencode/p2j/ui/client/driver/web/res/
  src/com/goldencode/p2j/ui/client/gui/driver/AbstractGuiDriver.java
  src/com/goldencode/p2j/ui/client/gui/driver/CursorType.java
  src/com/goldencode/p2j/ui/client/gui/driver/EmulatedWindowState.java
  src/com/goldencode/p2j/ui/client/gui/driver/ImageWrapper.java
  src/com/goldencode/p2j/ui/client/gui/driver/LineStroke.java
  src/com/goldencode/p2j/ui/client/gui/driver/swing/SwingFontMetrics.java
  src/com/goldencode/p2j/ui/client/gui/driver/web/
  src/com/goldencode/p2j/ui/client/gui/driver/web/GuiWebDriver.java
  src/com/goldencode/p2j/ui/client/gui/driver/web/GuiWebEmulatedWindow.java
  src/com/goldencode/p2j/ui/client/gui/driver/web/GuiWebPageHandler.java
  src/com/goldencode/p2j/ui/client/gui/driver/web/GuiWebSocket.java
  src/com/goldencode/p2j/ui/client/gui/driver/web/res/
  src/com/goldencode/p2j/ui/client/gui/driver/web/res/p2j.clipboard_helpers.js
  src/com/goldencode/p2j/ui/client/gui/driver/web/res/p2j.screen.js
  src/com/goldencode/p2j/util/ChildProcessFactory.java
  src/com/goldencode/p2j/util/InteractiveChildProcess.java
renamed:
  src/com/goldencode/p2j/ui/client/AbstractClient.java => src/com/goldencode/p2j/ui/client/driver/swing/AbstractClient.java
  src/com/goldencode/p2j/ui/client/chui/driver/swing/TerminalOptions.java => src/com/goldencode/p2j/ui/client/chui/driver/TerminalOptions.java
  src/com/goldencode/p2j/ui/client/chui/driver/swing/VT100SwingRenderer.java => src/com/goldencode/p2j/ui/client/chui/driver/VT100RendererImpl.java
  src/com/goldencode/p2j/ui/client/chui/driver/web/AbstractHandlerCommons.java => src/com/goldencode/p2j/web/AbstractHandlerCommons.java
  src/com/goldencode/p2j/ui/client/chui/driver/web/DojoToolkitHandler.java => src/com/goldencode/p2j/web/DojoToolkitHandler.java
  src/com/goldencode/p2j/ui/client/chui/driver/web/PushMessagesWorker.java => src/com/goldencode/p2j/ui/client/driver/web/PushMessagesWorker.java
  src/com/goldencode/p2j/ui/client/chui/driver/web/WatchdogTimer.java => src/com/goldencode/p2j/ui/client/driver/web/WatchdogTimer.java
  src/com/goldencode/p2j/ui/client/chui/driver/web/WebScreenDriver.java => src/com/goldencode/p2j/ui/client/driver/web/EmbeddedWebServer.java
  src/com/goldencode/p2j/ui/client/chui/driver/web/chui_web.html => src/com/goldencode/p2j/main/web_client.html
  src/com/goldencode/p2j/ui/client/chui/driver/web/index.html => src/com/goldencode/p2j/ui/client/driver/web/index.html
  src/com/goldencode/p2j/ui/client/chui/driver/web/res/beep.mp3 => src/com/goldencode/p2j/ui/client/driver/web/res/beep.mp3
  src/com/goldencode/p2j/ui/client/chui/driver/web/res/beep.ogg => src/com/goldencode/p2j/ui/client/driver/web/res/beep.ogg
  src/com/goldencode/p2j/ui/client/chui/driver/web/res/beep.wav => src/com/goldencode/p2j/ui/client/driver/web/res/beep.wav
  src/com/goldencode/p2j/ui/client/chui/driver/web/res/tty.clipboard.js => src/com/goldencode/p2j/ui/client/driver/web/res/p2j.clipboard.js
  src/com/goldencode/p2j/ui/client/chui/driver/web/res/tty.js => src/com/goldencode/p2j/ui/client/driver/web/res/p2j.js
  src/com/goldencode/p2j/ui/client/chui/driver/web/res/tty.keyboard.js => src/com/goldencode/p2j/ui/client/driver/web/res/p2j.keyboard.js
  src/com/goldencode/p2j/ui/client/chui/driver/web/res/tty.keymap.js => src/com/goldencode/p2j/ui/client/driver/web/res/p2j.keymap.js
  src/com/goldencode/p2j/ui/client/chui/driver/web/res/tty.screen.js => src/com/goldencode/p2j/ui/client/chui/driver/web/res/p2j.screen.js
  src/com/goldencode/p2j/ui/client/chui/driver/web/res/tty.socket.js => src/com/goldencode/p2j/ui/client/driver/web/res/p2j.socket.js
  src/com/goldencode/p2j/ui/client/chui/driver/web/res/tty.sound.js => src/com/goldencode/p2j/ui/client/driver/web/res/p2j.sound.js
  src/com/goldencode/p2j/ui/client/driver/GenericWebSocketCreator.java => src/com/goldencode/p2j/ui/client/driver/web/GenericWebSocketCreator.java
  src/com/goldencode/p2j/ui/client/driver/swing/AbstractSimulator.java => src/com/goldencode/p2j/ui/client/driver/swing/ContentPane.java
  src/com/goldencode/p2j/ui/client/gui/driver/GuiOptions.java => src/com/goldencode/p2j/ui/client/gui/driver/GuiConstants.java
  src/com/goldencode/p2j/ui/client/gui/driver/swing/EditableWidget.java => src/com/goldencode/p2j/ui/client/gui/driver/EditableWidget.java
  src/com/goldencode/p2j/ui/client/gui/driver/swing/GuiClient.java => src/com/goldencode/p2j/ui/client/gui/driver/swing/SwingGuiClient.java
  src/com/goldencode/p2j/ui/client/gui/driver/swing/GuiSimulator.java => src/com/goldencode/p2j/ui/client/gui/driver/swing/SwingEmulatedWindow.java
  src/com/goldencode/p2j/ui/client/gui/driver/swing/MouseHandler.java => src/com/goldencode/p2j/ui/client/gui/driver/MouseHandler.java
  src/com/goldencode/p2j/ui/client/gui/driver/swing/MouseMoveable.java => src/com/goldencode/p2j/ui/client/gui/driver/MouseMoveable.java
  src/com/goldencode/p2j/ui/client/gui/driver/swing/MousePopupable.java => src/com/goldencode/p2j/ui/client/gui/driver/MousePopupable.java
  src/com/goldencode/p2j/ui/client/gui/driver/swing/MouseResizeable.java => src/com/goldencode/p2j/ui/client/gui/driver/MouseResizeable.java
  src/com/goldencode/p2j/ui/client/gui/driver/swing/MouseTooltipWorker.java => src/com/goldencode/p2j/ui/client/gui/driver/MouseTooltipWorker.java
  src/com/goldencode/p2j/ui/client/gui/driver/swing/MouseWidgetAction.java => src/com/goldencode/p2j/ui/client/gui/driver/MouseWidgetAction.java
  src/com/goldencode/p2j/ui/client/gui/driver/swing/PaintPrimitives.java => src/com/goldencode/p2j/ui/client/gui/driver/PaintPrimitives.java
  src/com/goldencode/p2j/ui/client/gui/driver/swing/PaintStructure.java => src/com/goldencode/p2j/ui/client/gui/driver/PaintStructure.java
  src/com/goldencode/p2j/ui/client/gui/driver/swing/SwingGuiPrimitives.java => src/com/goldencode/p2j/ui/client/gui/driver/GuiPrimitivesImpl.java
  src/com/goldencode/p2j/ui/client/swing/ColorMapper.java => src/com/goldencode/p2j/ui/client/driver/swing/ColorMapper.java
  src/com/goldencode/p2j/ui/client/swing/ContainerOps.java => src/com/goldencode/p2j/ui/client/driver/swing/ContainerOps.java
  src/com/goldencode/p2j/ui/client/swing/ImageXfer.java => src/com/goldencode/p2j/ui/client/driver/swing/ImageXfer.java
  src/com/goldencode/p2j/ui/client/swing/LinuxKeyboardReader.java => src/com/goldencode/p2j/ui/client/driver/swing/LinuxKeyboardReader.java
  src/com/goldencode/p2j/ui/client/swing/PopupTrigger.java => src/com/goldencode/p2j/ui/client/driver/swing/PopupTrigger.java
  src/com/goldencode/p2j/ui/client/swing/SwingHelper.java => src/com/goldencode/p2j/ui/client/driver/swing/SwingHelper.java
  src/com/goldencode/p2j/ui/client/swing/SwingKeyboardReader.java => src/com/goldencode/p2j/ui/client/driver/swing/SwingKeyboardReader.java
  src/com/goldencode/p2j/ui/client/swing/WinKeyboardReader.java => src/com/goldencode/p2j/ui/client/driver/swing/WinKeyboardReader.java
modified:
  build.xml
  src/com/goldencode/p2j/main/Broker.java
  src/com/goldencode/p2j/main/ProcessClientSpawner.java
  src/com/goldencode/p2j/main/StandardServer.java
  src/com/goldencode/p2j/main/WebClientBuilder.java
  src/com/goldencode/p2j/main/WebClientBuilderOptions.java
  src/com/goldencode/p2j/main/WebClientBuilderParameters.java
  src/com/goldencode/p2j/main/WebClientSpawner.java
  src/com/goldencode/p2j/main/WebHandler.java
  src/com/goldencode/p2j/ui/ColorPalette.java
  src/com/goldencode/p2j/ui/ColorRgb.java
  src/com/goldencode/p2j/ui/ColorTable.java
  src/com/goldencode/p2j/ui/FrameWidget.java
  src/com/goldencode/p2j/ui/Keyboard.java
  src/com/goldencode/p2j/ui/chui/ThinClient.java
  src/com/goldencode/p2j/ui/client/Cell.java
  src/com/goldencode/p2j/ui/client/ColorManager.java
  src/com/goldencode/p2j/ui/client/FontManager.java
  src/com/goldencode/p2j/ui/client/OutputManager.java
  src/com/goldencode/p2j/ui/client/UiUtils.java
  src/com/goldencode/p2j/ui/client/chui/driver/ChuiPrimitives.java
  src/com/goldencode/p2j/ui/client/chui/driver/batch/BatchDriver.java
  src/com/goldencode/p2j/ui/client/chui/driver/batch/BatchHelper.java
  src/com/goldencode/p2j/ui/client/chui/driver/batch/BatchPrimitives.java
  src/com/goldencode/p2j/ui/client/chui/driver/console/ConsoleDriver.java
  src/com/goldencode/p2j/ui/client/chui/driver/console/ConsolePrimitives.java
  src/com/goldencode/p2j/ui/client/chui/driver/swing/ChuiClient.java
  src/com/goldencode/p2j/ui/client/chui/driver/swing/ChuiSimulator.java
  src/com/goldencode/p2j/ui/client/chui/driver/swing/SettingsPanel.java
  src/com/goldencode/p2j/ui/client/chui/driver/swing/SwingChuiDriver.java
  src/com/goldencode/p2j/ui/client/chui/driver/swing/TerminalMenu.java
  src/com/goldencode/p2j/ui/client/chui/driver/swing/VT100SwingKeyboard.java
  src/com/goldencode/p2j/ui/client/chui/driver/web/ChuiWebDriver.java
  src/com/goldencode/p2j/ui/client/chui/driver/web/ChuiWebPageHandler.java
  src/com/goldencode/p2j/ui/client/chui/driver/web/ChuiWebSimulator.java
  src/com/goldencode/p2j/ui/client/driver/ScreenDriver.java
  src/com/goldencode/p2j/ui/client/event/EventManager.java
  src/com/goldencode/p2j/ui/client/event/MouseEvt.java
  src/com/goldencode/p2j/ui/client/gui/ButtonGuiImpl.java
  src/com/goldencode/p2j/ui/client/gui/CaptionButton.java
  src/com/goldencode/p2j/ui/client/gui/FillInGuiImpl.java
  src/com/goldencode/p2j/ui/client/gui/FrameGuiImpl.java
  src/com/goldencode/p2j/ui/client/gui/GuiColorResolver.java
  src/com/goldencode/p2j/ui/client/gui/GuiFontResolver.java
  src/com/goldencode/p2j/ui/client/gui/ImageGuiImpl.java
  src/com/goldencode/p2j/ui/client/gui/LabelGuiImpl.java
  src/com/goldencode/p2j/ui/client/gui/PopupMenu.java
  src/com/goldencode/p2j/ui/client/gui/PopupOption.java
  src/com/goldencode/p2j/ui/client/gui/RectangleGuiImpl.java
  src/com/goldencode/p2j/ui/client/gui/ScrollBarGuiButton.java
  src/com/goldencode/p2j/ui/client/gui/ScrollBarGuiImpl.java
  src/com/goldencode/p2j/ui/client/gui/ScrollableListGuiImpl.java
  src/com/goldencode/p2j/ui/client/gui/StatusLineGuiImpl.java
  src/com/goldencode/p2j/ui/client/gui/TextGuiImpl.java
  src/com/goldencode/p2j/ui/client/gui/ToolTip.java
  src/com/goldencode/p2j/ui/client/gui/WindowGuiImpl.java
  src/com/goldencode/p2j/ui/client/gui/WindowLayout.java
  src/com/goldencode/p2j/ui/client/gui/WindowTitleBar.java
  src/com/goldencode/p2j/ui/client/gui/WindowWorkSpace.java
  src/com/goldencode/p2j/ui/client/gui/driver/GuiDriver.java
  src/com/goldencode/p2j/ui/client/gui/driver/GuiOutputManager.java
  src/com/goldencode/p2j/ui/client/gui/driver/GuiPrimitives.java
  src/com/goldencode/p2j/ui/client/gui/driver/swing/SwingGuiDriver.java
  src/com/goldencode/p2j/ui/client/widget/AbstractWidget.java
  src/com/goldencode/p2j/ui/client/widget/Widget.java
  src/com/goldencode/p2j/util/ProcessDaemon.java
  src/com/goldencode/terminal/VT100AbstractKeyboard.java
  src/com/goldencode/terminal/VT100AbstractRenderer.java
  src/com/goldencode/p2j/ui/client/driver/swing/AbstractClient.java
  src/com/goldencode/p2j/ui/client/chui/driver/TerminalOptions.java
  src/com/goldencode/p2j/ui/client/chui/driver/VT100RendererImpl.java
  src/com/goldencode/p2j/web/AbstractHandlerCommons.java
  src/com/goldencode/p2j/web/DojoToolkitHandler.java
  src/com/goldencode/p2j/ui/client/driver/web/PushMessagesWorker.java
  src/com/goldencode/p2j/ui/client/driver/web/WatchdogTimer.java
  src/com/goldencode/p2j/ui/client/driver/web/EmbeddedWebServer.java
  src/com/goldencode/p2j/main/web_client.html
  src/com/goldencode/p2j/ui/client/driver/web/index.html
  src/com/goldencode/p2j/ui/client/driver/web/res/p2j.clipboard.js
  src/com/goldencode/p2j/ui/client/driver/web/res/p2j.js
  src/com/goldencode/p2j/ui/client/driver/web/res/p2j.keyboard.js
  src/com/goldencode/p2j/ui/client/driver/web/res/p2j.keymap.js
  src/com/goldencode/p2j/ui/client/chui/driver/web/res/p2j.screen.js
  src/com/goldencode/p2j/ui/client/driver/web/res/p2j.socket.js
  src/com/goldencode/p2j/ui/client/driver/web/res/p2j.sound.js
  src/com/goldencode/p2j/ui/client/driver/web/GenericWebSocketCreator.java
  src/com/goldencode/p2j/ui/client/driver/swing/ContentPane.java
  src/com/goldencode/p2j/ui/client/gui/driver/GuiConstants.java
  src/com/goldencode/p2j/ui/client/gui/driver/EditableWidget.java
  src/com/goldencode/p2j/ui/client/gui/driver/swing/SwingGuiClient.java
  src/com/goldencode/p2j/ui/client/gui/driver/swing/SwingEmulatedWindow.java
  src/com/goldencode/p2j/ui/client/gui/driver/MouseHandler.java
  src/com/goldencode/p2j/ui/client/gui/driver/MouseMoveable.java
  src/com/goldencode/p2j/ui/client/gui/driver/MousePopupable.java
  src/com/goldencode/p2j/ui/client/gui/driver/MouseResizeable.java
  src/com/goldencode/p2j/ui/client/gui/driver/MouseTooltipWorker.java
  src/com/goldencode/p2j/ui/client/gui/driver/MouseWidgetAction.java
  src/com/goldencode/p2j/ui/client/gui/driver/PaintPrimitives.java
  src/com/goldencode/p2j/ui/client/gui/driver/PaintStructure.java
  src/com/goldencode/p2j/ui/client/gui/driver/GuiPrimitivesImpl.java
  src/com/goldencode/p2j/ui/client/driver/swing/ColorMapper.java
  src/com/goldencode/p2j/ui/client/driver/swing/ContainerOps.java
  src/com/goldencode/p2j/ui/client/driver/swing/ImageXfer.java
  src/com/goldencode/p2j/ui/client/driver/swing/LinuxKeyboardReader.java
  src/com/goldencode/p2j/ui/client/driver/swing/PopupTrigger.java
  src/com/goldencode/p2j/ui/client/driver/swing/SwingHelper.java
  src/com/goldencode/p2j/ui/client/driver/swing/SwingKeyboardReader.java
  src/com/goldencode/p2j/ui/client/driver/swing/WinKeyboardReader.java

In addition to the list of refactoring in note 651, I had to significantly refactor interactive process launching, because it was deeply integrated between ProcessDaemon and the UI simulator classes.

I have tested the ConsoleDriver and SwingChuiDriver with some standalone testcases, including process launching. I don't know of any bugs there.

I then tested the ConsoleDriver, BatchDriver, SwingChuiDriver and ChuiWebDriver using the IM POC code. The known issues which I'm working on so far:

  • Although I can execute interactive child processes in the swing ChUI client using standalone testcases, trying to start batchmaster in IM POC does not work in the swing client. It does work from the native client (ConsoleDriver), so something is regressed there. It might also be a regression in BatchDriver.
  • The ChUI web client is broken right now. I did make some pretty deep changes there, including loading resources from a list of locations so I suspect that is the issue. I don't know what else is hiding behind that.

General use of the IM POC code (batchmaster, menus, address create, address lookup) all worked well except what is noted above.

I haven't tried MAJIC yet.

Hynek/Constantin: please review this code.

Anton: please apply this code to your web client environment and debug the failure in the ChUI web client. You should be able to use any simple converted testcase that you already have. The failure occurs after the web client process is started but before the main "terminal" window can be brought up.

I will look at the process launching failure.

#655 Updated by Greg Shah almost 9 years ago

Two other notes about my update:

1. The node "chuiWeb" in the directory needs to be changed to "webClient".

2. The client:driver:type for swing_chui_frame is now chui_swing.

3. The client:driver:type for swing_gui_frame is now gui_swing.

I haven't tested GUI yet.

#656 Updated by Greg Shah almost 9 years ago

Here is the update merged with bzr 10849.

#657 Updated by Constantin Asofiei almost 9 years ago

Greg Shah wrote:

Here is the update merged with bzr 10849.

There is a compilation problem in WidgetBrowserAspect:181:

               driver.drawHighlight(loc.x, loc.y, dim.width, dim.height, Color.RED);

#658 Updated by Constantin Asofiei almost 9 years ago

Another issue: FontDetails.key is never set - it should be set by FontManager$WorkArea.getFont.

#659 Updated by Greg Shah almost 9 years ago

Both are fixed with this update.

#660 Updated by Greg Shah almost 9 years ago

Merged with bzr 10850.

#661 Updated by Greg Shah almost 9 years ago

Merged with bzr 10853.

#662 Updated by Greg Shah almost 9 years ago

#663 Updated by Greg Shah almost 9 years ago

Merged with bzr 10855.

#664 Updated by Anton Breaur almost 9 years ago

After investigating the web Chui issue, I found that the ChuiWebDriver.getWelcomePageUrl method builds the wrong URL, so the index.html cannot be found.
I have fixed the URL and added a ContextLoader for "/*" path so index.html loads now.
This doesn't eliminate the whole issue, as the other resources (p2j.js and modules, sound files) still cannot be loaded.
Dojo toolkit does load.
This should be related to the resource handlers for "/client/*" and "/common/*"

#665 Updated by Anton Breaur almost 9 years ago

Adding another subfolder to res folders as such: com/goldencode/p2j/ui/client/chui/driver/web/res/client and com/goldencode/p2j/ui/client/driver/web/res/common solves the static resource loading issue.
Although all the docs and examples point out that wraping a ResourceHandler under a ContextHandler should enable loading resources for given path from a specific folder, the url path is added to location as well, so that is why we need the suggested folder structure.
This, along with the update from note 664 solves the web chui failure.

#666 Updated by Greg Shah almost 9 years ago

After investigating the web Chui issue, I found that the ChuiWebDriver.getWelcomePageUrl method builds the wrong URL, so the index.html cannot be found.

Please provide the specific result that is incorrect so that I can understand what is wrong.

In addition, post the code that is your proposed fix.

#667 Updated by Greg Shah almost 9 years ago

Adding another subfolder to res folders as such: com/goldencode/p2j/ui/client/chui/driver/web/res/client and com/goldencode/p2j/ui/client/driver/web/res/common solves the static resource loading issue.

The described solution is exactly what we don't want to do. The whole point of how I've split things allows client-specific and client-generic code to be split up.

I am very surprised that we can't make things load from 3 separate directories. That makes no sense. Please provide more details on the specific Jetty limitations that can't be overcome.

Although all the docs and examples point out that wraping a ResourceHandler under a ContextHandler should enable loading resources for given path from a specific folder,

We are not just limited to using this approach.

the url path is added to location as well,

Please provide specifics here to explain exactly what you mean.

This, along with the update from note 664 solves the web chui failure.

How much have you tested "beyond" just loading the client? Have you executed real converted 4GL code?

#668 Updated by Greg Shah almost 9 years ago

We are not just limited to using this approach.

To be clear, my preference is to use predefined classes from Jetty to implement this common, client (gui) and client (chui) approach. I'd be surprised if that can't be done.

But if it really can't be done, then we can just implement our own solution as a single handler. Right?

#669 Updated by Anton Breaur almost 9 years ago

After investigating the web Chui issue, I found that the ChuiWebDriver.getWelcomePageUrl method builds the wrong URL, so the index.html cannot be found.

Please provide the specific result that is incorrect so that I can understand what is wrong.

In addition, post the code that is your proposed fix.

The ChuiWebDriver.getWelcomePageUrl uses ChuiWebPageHandler package name to point to index.xml as such:

StringBuilder sb = new StringBuilder(pageContext);
String packageName = EmbeddedWebServerImpl.class.getPackage().getName();
return sb.append(packageName.replace('.', '/')).append(pageFile).toString();

This is poiting to "com.goldencode.p2j.ui.client.chui.driver.web". Instead, index.html is located at: "com.goldencode.p2j.ui.client.driver.web", so we could use WebPageHandler package for that:
String packageName = WebPageHandler.class.getPackage().getName();

The described solution is exactly what we don't want to do. The whole point of how I've split things allows client-specific and client-generic code to be split up.

I am very surprised that we can't make things load from 3 separate directories. That makes no sense. Please provide more details on the specific Jetty limitations that can't be overcome.

Yes, I do agree with that. I will further investigate the ResourceHandler code. As I see now, the path url is added to the location of the resource in the jar, so when trying to load "/common/p2j.js", the resource handler will look for it in "/com/goldencode/p2j/ui/client/driver/web/res/common/p2j.js".

How much have you tested "beyond" just loading the client? Have you executed real converted 4GL code?

I have run the ask.p and primes.p testcase and it is working fine.

#670 Updated by Anton Breaur almost 9 years ago

Although all the docs and examples point out that wraping a ResourceHandler under a ContextHandler should enable loading resources for given path from a specific folder, the url path is added to location as well, so that is why we need the suggested folder structure.

I was wrong about this. The ResourceHandlers behave as they should. It was another ContextHandler I added that was messing with the normal handling flow. I removed it and got other results.

Besides the index.html problem I mentioned in note 664, the real issue was related to the HandlerCollection in GenericWebServer.configWsContext method.
The handler collection kept all the needed specific handlers (web page, resource and dojo handlers) in the right order and it was "wrapped" in a ContextHandler with path "/" and resource base "." .
It looks like that context handler was intended to pass the requests to web page handlers, but it was sitting on top of the whole handler collection.
The requests never make it into the HandlerCollection and the result is a 404 not found.

I had removed this parent ContextHandler and wrapped each ChuiWebPageHandler instance under a ContextHandler("/"). This means that the server handler is the direct handler collection, so the GenericWebServer.configWsContext only has to return the handlerCollection.

In EmbeddedWebServerImpl.startupServer() :

// process each handler
for (int i = 0; i < hdlrs.length; i++) {
hdlrs[i].setAuthorizationToken(token);
ContextHandler contextHandler = new ContextHandler();
contextHandler.setHandler(hdlrs[i]);
server.addHandler(contextHandler);
}

In GenericWebServer :

private Handler configWsContext() {
// This commented code is not needed
// ContextHandler context = new ContextHandler();
// context.setContextPath(contextPath);
// context.setClassLoader(Thread.currentThread().getContextClassLoader());
//
// context.setHandler(handlerCollection);
return handlerCollection;
}

The method name and javadoc should be updated, but I left it as it is for review reasons.

Ask.p and primes.p are working as expected with these changes.

#671 Updated by Greg Shah almost 9 years ago

Please post your changed versions of my files (only include those files you have changed).

#672 Updated by Greg Shah almost 9 years ago

Merged to bzr 10863.

#673 Updated by Greg Shah almost 9 years ago

This includes a fix for the batch process launching from Swing clients.

I have also tried to implement the fixes for the ChUI web client, as described above. Those are included. Although I can now get to the index.html, successfully login to the OS and create a client. The web_client.html page that is loaded does not display anything.

Either I don't fully understand what was reported above or something is missing.

Anton, please post the necessary changes ASAP. The web client problems are the only known problems right now.

I'm going to test this update with MAJIC next.

#674 Updated by Anton Breaur almost 9 years ago

Greg, I don't understand the web_client.html issue. The web chui client does not need that resource.
If you applied my changes and index.html is loading, the rest should be loading as well.
I will post my changes asap.

#675 Updated by Greg Shah almost 9 years ago

The web_client.html is the login page. This works with my 0515b update. It allows the ChUI-specific index.html to be loaded. This part loads but is probably not loading all the associated resources as the result is a blank page.

#676 Updated by Greg Shah almost 9 years ago

I ran main regression testing on the 0515b update last night. GSO 418 did fail in addition to the expected failure in step 40 of tc_job_002. I manually retested GSO 418 and the failure could not be duplicated. I consider main regression testing passed.

I am running CTRL-C testing now.

My plan is to integrate the ChUI web client fix (it should be in code that can't be reached in regression testing) and check this in, assuming the CTRL-C testing passes.

I have also seen a potential regression in GUI where the fonts don't look right. I've asked Constantin to look at it. I'll integrate that change as well (it probably won't require any regression testing as it is likely in GUI-specific code) before check in.

#677 Updated by Anton Breaur almost 9 years ago

The web_client.html is the login page. This works with my 0515b update. It allows the ChUI-specific index.html to be loaded. This part loads but is probably not loading all the associated resources as the result is a blank page.

This works for me. I am packing the classes now and doing another test.

#678 Updated by Anton Breaur almost 9 years ago

This fixes the web chui failure. It was created on top of the merged with revision 10855 version, and not yet tested for the 10863 merge.

#679 Updated by Anton Breaur almost 9 years ago

The latest patch works fine with revision 10863 merged version.
I still don't understand the web_client.html issue. This is the URL I am working with: https://localhost:7443/chui
This loads the operating system login and after successful login it redirects to https://localhost:7449/index.html
The index.html loads all the needed common and client resources and the p2j converted code runs well.

#680 Updated by Greg Shah almost 9 years ago

I'm merging and testing your changes. The important difference is in the EmbeddedWebServerImpl.startupServer().

#681 Updated by Greg Shah almost 9 years ago

0515b passed CTRL-C testing.

The attached is 0515b merged with the changes in ab_upd20150516a.zip. The web ChUI client now works. After testing with a more extensive application, everything is working well (including child process launching and batch/background client process launching).

At this point the only known issue is that the fonts in GUI seem off. I made quite a significant set of changes in that area so I'm guessing I broke something (or more than one something).

On the other hand, this set of changes is so substantial that I think it may make sense to check this in. Any font changes will likely be only in GUI code and can be checked in without the MAJIC regression testing.

Constantin/Hynek: please review this code. Do you have any objections or concerns?

#682 Updated by Greg Shah almost 9 years ago

Code Review ab_upd20150516a.zip

Anton: although I have already merged your changes with my own, I am providing this code review so that future updates will be closer to standard. You do not have to fix the current update.

1. The update zip itself should only include paths starting with src/. The ab_upd20150516a/ directory should not be there. Usually, we just zip up the update from within the p2j/ top level directory. Please look at my update if you have any questions.

2. All 3 files were missing history entries.

3. In ChuiWebDriver, this line:

Resource cpres = EmbeddedWebServerImpl.calcResourceBase(ChuiWebPageHandler.class, "res");

My only concern here is that we can just as easily use ChuiWebDriver.class to get the same result. By using ChuiWebPageHandler.class you have added an extra linkage between the two classes. It is better not to have this dependency if not needed.

4. In EmbeddedWebServerImpl, your IDE has added a range of extra imports that are completely unnecessary. In addition, even when adding imports that are needed, per coding standards they should be wildcard imports unless there is a specific reason to deviate. Generally, the only reasons we've had for deviating is if there is a naming conflict such that we must import a specific class OR for imports that are something that will be removed later such that we want to put a comment explaining that the import is being used deliberately and will be removed in the future.

5. In EmbeddedWebServerImpl, you have inserted hard tab characters. This should not be done. Your IDE should be configured to replace hard tabs with 3 spaces per tab.

#683 Updated by Anton Breaur almost 9 years ago

I will fix these and upload an update.

#684 Updated by Greg Shah almost 9 years ago

As I mentioned in note 682:

You do not have to fix the current update.

I just added the bold to highlight the NOT. I provided the feedback for future reference, not because I need the ab_upd20150516a.zip to be fixed.

#685 Updated by Anton Breaur almost 9 years ago

Oh, alright. I will keep those standards in mind then.

#686 Updated by Greg Shah almost 9 years ago

Attached is the latest scripts to help review or apply the update.

#687 Updated by Greg Shah almost 9 years ago

Anton: here is your next task.

Please review the following from my update:

src/com/goldencode/p2j/ui/client/FontDetails.java
src/com/goldencode/p2j/ui/client/FontManager.java
src/com/goldencode/p2j/ui/client/FontMetricsHelper.java
src/com/goldencode/p2j/ui/client/FontStyle.java
src/com/goldencode/p2j/ui/client/gui/driver/swing/SwingFontMetrics.java
src/com/goldencode/p2j/ui/client/gui/GuiFontResolver.java
src/com/goldencode/p2j/ui/client/gui/driver/GuiDriver.java (createFont(), setGuiFont(), scaleFont(), setFontStyle(), 
getFontWidth(), getFontMaxWidth(), getFontHeight(), getTextWidth(), getTextHeight())
src/com/goldencode/p2j/ui/client/gui/driver/AbstractGuiDriver.java (deriveFont(), setGuiFont(), scaleFont(), setFontStyle(), getFontWidth(), getFontMaxWidth(), getFontHeight(), getTextWidth(), getTextHeight())
src/com/goldencode/p2j/ui/client/gui/driver/EmulatedWindowState.java (getFontMetrics(), getTextHeight())
src/com/goldencode/p2j/ui/client/gui/driver/swing/SwingGuiDriver.java (createFont(), deriveFont())
src/com/goldencode/p2j/ui/client/gui/driver/swing/SwingEmulatedWindow.java (draw() - see setFont() calls there, convertFontStyle(), getFontMetrics(), getTextHeight(), getNativeFontMetrics(), drawStringScaled(), drawString())

Based on this, you should have a good idea of how the font support works. Obviously, the classes in the swing/ subdirectory are the "native" driver font support.

Your task is to propose an approach for how to implement this native porton in javascript. We will have to implement some custom communications to send and recv this data between Java and javascript. Don't worry about that right now. Just come up with an approach that can do what the Swing native layer does.

I want you to show real JS code that can do each of the needed functions.

In addition, as noted far above in this task, this must be supported in a cross-browser approach (Firefox, Chrome, IE 11 and a recent Safari).

If the needed support is not available directly in the browser, I can accept the use of something like opentype.js (http://nodebox.github.io/opentype.js/ - which uses the MIT license, which is OK for us).

Document your results here, each day so that we can discuss them as you go.

#688 Updated by Constantin Asofiei almost 9 years ago

Greg Shah wrote:

At this point the only known issue is that the fonts in GUI seem off. I made quite a significant set of changes in that area so I'm guessing I broke something (or more than one something).

The problems were two:
  1. FontDetails.pointSize is not set before scaling the font
  2. SwinGuiDriver.createFont, when it scales the font, I think it assumes that the given fd object will be changed; this is not the case - scaleFont creates a copy and returns that copy, it doesn't change the given fd object.

See attached for the fix.

#689 Updated by Greg Shah almost 9 years ago

Code Review ca_upd20150516a.zip

The changes make sense. I've integrated them with my code, but I've done it a bit differently. I post more in the next entry.

#690 Updated by Greg Shah almost 9 years ago

This update has the ca_upd20150516a.zip changes merged.

My original scaleFont() implementation did not edit the existing FontDetails because the original GuiSimulator.scaleFont() returned a new instance of the passed in Font. The new version only uses the GuiDriver.scaleFont() from one place so it is safe to just edit the given FontDetails.

I've also moved the call to scaleFont() to FontManager.getFont() to make the createFont() implementation simpler. Since the scaleFont() lives in AbstractGuiDriver, it is common code too.

Constantin/Hynek: please review this code. I'll check it in unless there are objections.

#692 Updated by Hynek Cihlar almost 9 years ago

Review of ges_upd20150517a.zip.

This is a lot of nice cleanup!

  • ThinClient.resume() - shouldn't be the batch mode activated only when already active before suspend()?
  • PaintStructure.strokeWidth is not used in SwingEmulatedWindow.draw(), that is the width in AbstractGuiDriver.setLineStroke() doesn't seem to have any effect.
  • ColorTable.java, Keyboard.java are missing header entries.
  • Some unused imports, especially in GuiDriver, AbstractChuiDriver, EmulatedTerminalState, VT100EmulatorChildProcess, VT100RendererImpl, BatchDriver, BatchPrimitives, ConsoleDriver, ChuiWebDriver, ChuiWebPageHandler, AbstractDriver, SwingEmulatedWindow, AbstractGuiDriver, EmulatedWindowState, GuiOutputManager, GuiPrimitivesImpl, MouseHandler, MouseResizeable, MouseTooltipWorker, SwingEmulatedWindow, SwingGuiDriver, com.goldencode.p2j.ui.client.gui.driver.web.*,
  • ColorRgb ctor params are not aligned in ComboBoxGuiImpl.initialize().
  • FrameGuiImpl.java has no changes except of the file abstract.

#693 Updated by Constantin Asofiei almost 9 years ago

Greg Shah wrote:

Constantin/Hynek: please review this code. I'll check it in unless there are objections.

Beside what Hynek mentioned, I'm OK with the changes.

Hynek:

ThinClient.resume() - shouldn't be the batch mode activated only when already active before suspend()?

The OutputManager.activateBatch is a no-op if the driver is not a BatchDriver instance. Also, I think it's the responsibility of the code which uses TC.suspend and TC.resume to properly balance these calls. Another concern is hat TC.resume may be called twice in a row, in ProcessDaemon.launch's finally block and on line 212 - I think this is safe for now, as this only sets some flags and re-draws the windows, but it might become problematic at some point, if more complex operations are introduced.

#694 Updated by Greg Shah almost 9 years ago

Attached is the fixed update.

ThinClient.resume() - shouldn't be the batch mode activated only when already active before suspend()?

As Constantin notes, the batch mode stuff is a no-op except in the batch driver. In addition, this code was called unconditionally before my update. I just moved it to be more obvious that it matches up with suspend/resume processing.

Also, I think it's the responsibility of the code which uses TC.suspend and TC.resume to properly balance these calls. Another concern is hat TC.resume may be called twice in a row, in ProcessDaemon.launch's finally block and on line 212

This is a good point. Although this same issue existed before my update, I have put a flag in ProcessDaemon to ensure that resume only gets called once. Because of this change, I'm running a round of main regression testing right now. My standalone tests of process launching were fine, but this is just to be safe. I doubt the double-invocation case gets triggered in MAJIC.

PaintStructure.strokeWidth is not used in SwingEmulatedWindow.draw(), that is the width in AbstractGuiDriver.setLineStroke() doesn't seem to have any effect.

Fixed, good catch.

ColorTable.java, Keyboard.java are missing header entries.

Fixed.

Some unused imports,

Fixed.

ColorRgb ctor params are not aligned in ComboBoxGuiImpl.initialize().

Fixed.

FrameGuiImpl.java has no changes except of the file abstract.

Removed from the update.

Please take a quick look. If you see anything else, let me know. Otherwise I plan to check this in unless regression testing fails.

#695 Updated by Anton Breaur almost 9 years ago

Your task is to propose an approach for how to implement this native porton in javascript. We will have to implement some custom communications to send and recv this data between Java and javascript. Don't worry about that right now. Just come up with an approach that can do what the Swing native layer does.

I have reviewed the classes you listed and I got an understanding of how fonts are dealt with.
Attached is a small refactor for the FontManager class, which I did while browsing the code. It looked like some piece of code was repeating several times.

The biggest obstacle is implementing a similar SwingEmulatedWindow.drawStringScaled for GuiWebEmulatedWindow
This is scaling the font accordingly to obtain a legacy width and height of the painted text.
The canvas methods for drawing text do not allow a non linear scale of the font. The maximum you can get with using the fillText method is by adding a maxWidth parameter, which sets a limit for the printed text width, leaving the height unchanged, like in this example: http://www.w3schools.com/tags/playcanvas.asp?filename=playcanvas_filltextmaxwidth
This means that we can adjust the font-size property to fit the legacy height and then apply the maxWidth, which renders a non-exact result if the legacy width of the text is bigger than the natural width for the size we set up (the text will appear shorter).
I assume this would not be accepted.

opentype.js does not itself provide such a scaling method, but it can load truetype fonts and provides access to glyphs.
This means that with a bit of effort, it can be extended with the functionality we need.
Also, we could use opentype.js to implement just the drawStringScaled, as for the others, we can write our own (not too complicated) javascript code.

I am not sure that this could be valuable, but with opentype.js we can load external .ttf files as well.

#696 Updated by Hynek Cihlar almost 9 years ago

No objections to the changes in ges_upd20150518a.zip here.

#697 Updated by Greg Shah almost 9 years ago

Merged with bzr rev 10866. This only changes ColorTable and FrameWidget. Since my changes are just some javadoc/comment cleanup, there is no need to re-test.

The main regression testing for ges_upd20150518a.zip passed, so I am going to check-in and distribute ges_upd20150518b.zip.

#698 Updated by Greg Shah almost 9 years ago

A description of the more rational driver structure is as follows:

com/goldencode/p2j/ui/client/driver/ - Driver code that is common to both ChUI and GUI. This code is shared across all drivers.
com/goldencode/p2j/ui/client/driver/{web,swing}/ - Driver code that is common to both ChUI and GUI, but which provides functionality for a specific driver UI technology like javascript or Swing. This code is shared across all drivers using the same foundational UI technology (e.g. all Swing drivers would use the code in the swing subdirectory).

com/goldencode/p2j/ui/client/chui/driver/ - Driver code shared by all ChUI drivers.
com/goldencode/p2j/ui/client/chui/driver/{batch,console,swing,web}/ - Actual ChUI driver implementation (that is specific to a given UI technology).

com/goldencode/p2j/ui/client/gui/driver/ - Driver code shared by all ChUI drivers.
com/goldencode/p2j/ui/client/gui/driver/{swing,web}/ - Actual ChUI driver implementation (that is specific to a given UI technology).

I hope it is easier to understand and to find things. The changes should also result in significantly less duplication of code. In particular, the GuiPrimitives is now minimized and most functionality is in the GuiDriver interface itself which is mostly implemented in the common code for AbstractGuiDriver. The per-window code in GUI has been minimized and the common code is in EmulatedWindowState. Specific GUI drivers will provide subclasses for both of these.

A final note: we now only have a very small number of AWT/Swing references that are outside of the 3 swing packages (and the widget browser). These cases are mostly for event processing and we are just reusing the event classes as data structures. We are not actually using the AWT/Swing event model in those places. I have marked each such import location with a comment to describe this. DO NOT add AWT/Swing references to any of the common code unless you have explicit authorization to do so from your team leader.

#699 Updated by Greg Shah almost 9 years ago

I have executed local testing of all client types, using the IM POC code for the 4 ChUI clients. MAJIC regression testing has also passed.

ges_upd20150518b.zip is checked in as bzr rev 10867 (refs #1811).

#700 Updated by Greg Shah almost 9 years ago

I assume this would not be accepted.

Correct.

So far, your proposal sounds reasonable. Do you have more research to do or are you ready to work on specific javascript code to implement these features?

#701 Updated by Anton Breaur almost 9 years ago

Do you have more research to do or are you ready to work on specific javascript code to implement these features?

I am starting to implement javascript modules for the needed features.

#702 Updated by Greg Shah almost 9 years ago

Code Review ab_upd20150518a.zip

I'm good with the changes.

Constantin: any objections?

#703 Updated by Constantin Asofiei almost 9 years ago

Greg Shah wrote:

Code Review ab_upd20150518a.zip

I'm good with the changes.

Constantin: any objections?

  1. there is an extra empty line on 587
  2. instead of using TitledWindow, lets change all of these to Window, as this class can work only with a WINDOW 4GL resource. Also, TitledWindow is used for other kinds of pseudo-windows (like the combo drop-down or alert-box), which don't have their own font environment.

#704 Updated by Greg Shah almost 9 years ago

The ab_upd20150518a.zip conflicts with the FontManager changes in ca_upd20150519b.zip. How do we want to sequence this?

Anton: although your changes look low risk, they do get use in ChUI code. This means that the MAJIC regression testing will need to be executed and passed before this is checked in. Because of the file conflict with Constantin's update, we need to decide who "goes first" (tests and then checks-in their change first). Then the person going second will merge, test and check in. For the 2nd person, there is no need to run regression testing until after the merge because generally they would have to re-test post-merge anyway.

In preparation for the testing, please review the timco.html document in case you have not yet read it.

#705 Updated by Anton Breaur almost 9 years ago

I have attached a partial implementation for drawString and drawStringScaled methods in javascript. The index.html demonstrates these.
The drawString function is implemented using the canvas only, while drawStringScaled is using opentype.
This uses a hacked version of opentype.js which accepts a widthScale parameter for drawing glyphs. For sure, a final version would extend opentype instead of modifying the original code.

There are several new obstacles I have discovered:

First, opentype deals with fontSize in pixels only. From what I see, we need to specify the size in points, as in awt fonts. I don't yet have a solution in mind for this, so I have implemented the draw functions with size in pixels, just to be consistent.

Then, opentype cannot load system fonts, just fonts from URL (file system or external). I think we could send the byte[] font definition from FontDetails over the network, but for now I just loaded a local Arial.ttf file.

At this time, it looks like opentype is no so close to what we want and it requires quite an effort to get it there, but I cannot find other libraries that would suit.
I am aware the code is looking really raw, but please advise on the approach and opentype.

#706 Updated by Greg Shah almost 9 years ago

I have attached a partial implementation for drawString and drawStringScaled methods in javascript. The index.html demonstrates these.
The drawString function is implemented using the canvas only, while drawStringScaled is using opentype.

I haven't been able to look at this yet. I'll do that later today.

First, opentype deals with fontSize in pixels only. From what I see, we need to specify the size in points, as in awt fonts. I don't yet have a solution in mind for this, so I have implemented the draw functions with size in pixels, just to be consistent.

A point is 1/72 of an inch. A CSS pixel is 1/96 of an inch (all web browsers are hard coded to 96 DPI). We should be able to scale the fonts to duplicate a specific point size. Please think about the approach and document it here.

Then, opentype cannot load system fonts, just fonts from URL (file system or external). I think we could send the byte[] font definition from FontDetails over the network, but for now I just loaded a local Arial.ttf file.

Please look at the default fonts list used for the 4GL. Can we map these to suitable web fonts? Can a web font be used via URL from opentype.js?

#707 Updated by Anton Breaur almost 9 years ago

I gave up opentype.js and took a different approach. We can apply a scaling transformation to the canvas context, and this allows to fit text in whatever height and width desired. After drawing a scaled text, we then apply the inverse transformation to get the canvas context in the initial state. It looks to me that this covers our needs and is much simpler.

#708 Updated by Greg Shah almost 9 years ago

I'm OK with not using opentype.js, so long as we can get everything implemented which we need. I do worry that we won't have access to all the metrics that we need, but I'm happy to avoid custom font rendering code.

However, I think you should not take the approach of an inverse transformation. The reason is simple: every scaled string draw will result in the canvas' transformation list growing by 2 transforms. These will accumulate and cause ever increasing yet useless transformation CPU overhead to the long lived canvas.

I think the proper way is to use save() to store off the context before drawing the scaled string and then restore() to clear the current transform and reset back to the prior state.

#709 Updated by Anton Breaur almost 9 years ago

I think the proper way is to use save() to store off the context before drawing the scaled string and then restore() to clear the current transform and reset back to the prior state.

Yes, definitely better this way.

I do worry that we won't have access to all the metrics that we need, but I'm happy to avoid custom font rendering code.

I am looking into this right now.

#710 Updated by Anton Breaur almost 9 years ago

I do worry that we won't have access to all the metrics that we need, but I'm happy to avoid custom font rendering code.

The problem is calculating the line height as the descent + leading + ascent.
I found a library called Font.js which does that and some other stuff we don't need and it has a function that paints to an off screen canvas then measures the text (actually scanning the painted image pixel by pixel ...); the function is quite tightly coupled with the other functionality, so I am trying to figure out how it works and replicate these measurements without having to import the whole.

#711 Updated by Greg Shah almost 9 years ago

Anton Breaur wrote:

I do worry that we won't have access to all the metrics that we need, but I'm happy to avoid custom font rendering code.

The problem is calculating the line height as the descent + leading + ascent.
I found a library called Font.js which does that and some other stuff we don't need and it has a function that paints to an off screen canvas then measures the text (actually scanning the painted image pixel by pixel ...); the function is quite tightly coupled with the other functionality, so I am trying to figure out how it works and replicate these measurements without having to import the whole.

Yes, we do exactly this already using our own code. Please see p2j.screen.js and look for the fontMetrics variable. I would prefer to extend that with the needed processing, rather than pull in a new dependency.

#712 Updated by Anton Breaur almost 9 years ago

Added the fontMetrics module from p2j.screen.js and managed to compute all the metrics described in com.goldencode.p2j.ui.client.FontMetricsHelper interface

Attached is the latest source.

#713 Updated by Greg Shah almost 9 years ago

Code Review fonts_js.zip from note 712

Overall, this is moving in the right direction.

Next steps:

1. Consider the fonts that are defined in src/font-metrics.xml and FontTable.standardFontTable. We need to know how the javascript/browser implementation handles that specific list of fonts (and their specified attributes). Please test a set of different strings to detect the metrics for each of those standard fonts. I'd like you to compare the results against our Swing implementation and in the 4GL. This will give us an idea of what problems have to be solved with the implementation.

2. I'd like you to add error handling and to make this code more bullet-proof.

#714 Updated by Anton Breaur almost 9 years ago

1. Consider the fonts that are defined in src/font-metrics.xml and FontTable.standardFontTable.

From what I see there, we need to support:
MS Sans Serif
Courier New
Segoe UI
Microsoft Sans Serif
System
FixedSys
Tahoma

On my Ubuntu machine, I see Courier new is available with ms truetype fonts package, Microsoft Sans Serif, Tahoma and Segoe UI are downloadable and installable, but I can't find MS Sans Serif, System and FixedSys.
I am looking into mapping all these microsoft fonts to similar web safe fonts so we can have a fallback if not installed.

#715 Updated by Anton Breaur almost 9 years ago

The way I envisioned the flow to work is that we send font information to the web client, like font name, size and style and the browser would load the operating system font.
As the fonts mentioned above are all microsoft licensed, these are not available on a standard linux machine, so a web client running in a linux browser would not be able to load "Segoe UI", for example.

My thought was that we could find metric compatible fonts to fallback on such situations, but it seems quite a difficult task.

Another approach would be to load a similar looking font and use scaling to match the desired metrics.

#716 Updated by Greg Shah almost 9 years ago

Please provide more details about exactly what you have tried to do and the results of each of those experiments/efforts. This will ensure that we don't have to re-do your work.

I also assume that you have some updated code.

#717 Updated by Constantin Asofiei almost 9 years ago

Anton Breaur wrote:

The way I envisioned the flow to work is that we send font information to the web client, like font name, size and style and the browser would load the operating system font.
As the fonts mentioned above are all microsoft licensed, these are not available on a standard linux machine, so a web client running in a linux browser would not be able to load "Segoe UI", for example.

My thought was that we could find metric compatible fonts to fallback on such situations, but it seems quite a difficult task.

The catch here is that 4GL code can refer only font indexes, not font names. So we can build a font-table with metric-compatible fonts. And if the target OS is expected to not provide these fonts, we can define these as "custom fonts" and pass to the web browser a link with the font definition - assuming that browsers can load fonts from URLs.

For example, I was using this custom-fonts node (per server), to test how Windows fonts are rendered on linux:

        <node class="container" name="custom-fonts">
          <node class="container" name="font1">
            <node class="string" name="font">
              <node-attribute name="value" value="Tahoma"/>
            </node>
            <node class="string" name="file">
              <node-attribute name="value" value="tahoma.ttf"/>
            </node>
          </node>
          <node class="container" name="font2">
            <node class="string" name="font">
              <node-attribute name="value" value="Courier New"/>
            </node>
            <node class="string" name="file">
              <node-attribute name="value" value="cour.ttf"/>
            </node>
          </node>
          <node class="container" name="font3">
            <node class="string" name="font">
              <node-attribute name="value" value="Segue UI"/>
            </node>
            <node class="string" name="file">
              <node-attribute name="value" value="segoeui.ttf"/>
            </node>
          </node>
        </node>

The font1, font2, font3 are just unique keys under the custom-fonts node, they do not refer to the font table. The mapping is done via the lower-cased font name.

#718 Updated by Greg Shah almost 9 years ago

And if the target OS is expected to not provide these fonts, we can define these as "custom fonts" and pass to the web browser a link with the font definition - assuming that browsers can load fonts from URLs.

Yes, browsers can load "web fonts". Modern browsers include support for loading fonts from .ttf files. As noted above, opentype.js allows direct loading of a truetype font as well.

Some useful resources for web fonts:

http://www.html5rocks.com/en/tutorials/webfonts/quick/
http://www.w3schools.com/css/css3_fonts.asp
http://www.w3schools.com/cssref/css_websafe_fonts.asp
https://www.google.com/fonts

#719 Updated by Greg Shah almost 9 years ago

Created task branch 1811o from P2J trunk revision 10893.

There are 14 previous commits from this task. For this reason, I am starting the task branch names at 'o'.

#720 Updated by Greg Shah almost 9 years ago

Hynek:

The AbstractGuiDriver.newChildWindowEmulator() is only ever called from GuiOutputManager.realizeChildWindow() (through some intermediate callers). In turn, GuiOutputManager.realizeChildWindow() is only ever called from ModalWindow.show() and the modal flag is already true.

Do you anticipate that GuiOutputManager.realizeChildWindow() will ever be called with modal as false? If so, what use cases do you expect?

I ask because I am implementing the web version of newChildWindowEmulator() and I want to know how much functionality is really needed.

#721 Updated by Hynek Cihlar almost 9 years ago

Greg Shah wrote:

Hynek:

The AbstractGuiDriver.newChildWindowEmulator() is only ever called from GuiOutputManager.realizeChildWindow() (through some intermediate callers). In turn, GuiOutputManager.realizeChildWindow() is only ever called from ModalWindow.show() and the modal flag is already true.

Do you anticipate that GuiOutputManager.realizeChildWindow() will ever be called with modal as false? If so, what use cases do you expect?

All the possible cases known to me are alert-box widget, dialog-box widget and (possibly) combo-box widget popup. The first two are for sure modal. The last one, combo-box widget popup, should be modaless. The reason for this is that the popup must go away when it looses focus for example by activating other top level window.

I ask because I am implementing the web version of newChildWindowEmulator() and I want to know how much functionality is really needed.

I am just about to finalize task branch 2559e which contains the rework of top level window modality (and some other changes related to top-level windows). In this branch the modality responsibility is taken away from the driver and moved up to WindowManager. And so the modal flag is no more part of newChildWindowEmulator() argument list. I suggest you wait until this is released.

#722 Updated by Greg Shah almost 9 years ago

The current clipboard support for the ChUI web client has many of the building blocks that are needed for GUI clipboard support, however the solution is not an exact match. With ChUI, copy/paste is just being used to allow the system clipboard to be used for text input (paste) and for capturing the contents of screens (copy) to the clipboard.

This ChUI support is NOT integrated with the CLIPBOARD system handle (which does work in ChUI, but is simply a private, process-internal clipboard implementation). This ChUI support is also not integrated with the CUT, COPY or PASTE key functions (by default these are F10, F11 and F12 respectively). Although we will have to add support at some time for the clipboard and cut/copy/paste functions, this support will be separate from the system clipboard implementation in the web client. See #1807.

In GUI, the support needed is different, the use of the system clipboard is not a convenience for input or screen capture. Rather, the 4GL is aware of and integrated with the system clipboard. Also, the CUT/COPY/PASTE key functions are backed by the system clipboard. We need to integrate the system clipboard on the GUI web client (via the browser and javascript), with the clipboard handle and cut/copy/paste implementation in the java client.

One problem area is that the clipboard is supposed to be inaccessible except if the end user generates a cut/copy/paste event (e.g. most commonly using key combinations CTRL-X for cut, CTRL-C for copy, CTRL-V for paste). If the user makes a selection from the browser's menu, this should also generate clipboard events and allow access to javascript.

But the use of synthetic events for system clipboard access is disallowed unless the browser provides some application-specific or site-specific configuration value. Internet Explorer is unusual in that it provides access outside of the clipboard events.

Because of this restriction, we have a problem in regard to support for CLIPBOARD:VALUE, which in GUI allows the 4GL programmer to read/write the system clipboard at any time. By definition, it will NOT generate a user event that will allow access.

Likewise, we need to check to see if CUT/COPY/PASTE can be generated by APPLY. If these read/write the system clipboard in GUI, then they would be another way for a programmer to access the clipboard outside of a user event.

Both cases are a problem for the web client.

https://www.lucidchart.com/techblog/2014/12/02/definitive-guide-copying-pasting-javascript/

Some ideas to be explored:

1. If the user generates a cut/copy/paste event, can we save off the event.clipboardData reference and then access that at any time in the future (outside of the context of the event)? The HTML5 Clipboard API suggests this may be possible. Note that it also suggests this is an issue to resolve in the future.

https://developer.mozilla.org/en-US/docs/Web/API/ClipboardEvent/clipboardData
https://w3c.github.io/clipboard-apis/#widl-ClipboardEvent-clipboardData
http://www.w3.org/TR/clipboard-apis/#fire-a-clipboard-event
https://msdn.microsoft.com/en-us/library/ms535220%28v=vs.85%29.aspx

2. Can simulated keystrokes be used to trigger clipboard events? This is one level more indirect than creating synthetic clipboard events, perhaps there is some possibility here. I wonder if that might work if we were already within the context of a user input event (e.g. mouse move which is something that should happen often).

http://stackoverflow.com/questions/21303965/mouse-click-simulates-keyboard-key-in-javascript
http://stackoverflow.com/questions/6157929/how-to-simulate-a-mouse-click-using-javascript
https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent
http://www.w3.org/TR/DOM-Level-3-Events/#events-keyboardevents
http://www.w3.org/TR/dom/#interface-event
https://msdn.microsoft.com/en-us/library/dn905219%28v=vs.85%29.aspx

3. Can document.execCommand() be used to generate copy/paste events?

https://gist.github.com/JamesMGreene/ec05eca43b1433a6a663
http://help.dottoro.com/ljctuhrg.php
https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand

This research will be deferred until #1807. For now, I will refactor the client-side clipboard support to maximize what can be shared and will implement some provisional support that will work when driven by a user-generated event.

#723 Updated by Greg Shah almost 9 years ago

A first pass implementation of clipboard support is checked in to task branch 1811o. The rest of the work will be deferred. The following TODOs remain:

1. GUI-compatible key processing is not yet ready. The following alternate clipboard key combinations cannot be generated yet:

COPY via SHIFT-INS (key 1022 in GUI, key 38 in ChUI)
CUT via SHIFT-DEL (key 639 in GUI, key -1 in ChUI)
PASTE via CTRL-INS (key 2558 in GUI, does not generate a key in ChUI)

We need to make sure these keys get generated by the web client.

2. After GUI-compatible key processing is available, p2j.keyboard.sendKey() will need to be updated to recognize the alternate keys and handle the prep appropriately. This should only occur in GUI.

3. In GUI, the key sequences that generate COPY or CUT clipboard events must be sent to the server to generate proper key events there. This is not being done today (see p2j.keyboard.sendKey()). PASTE is handled already (see GuiWebSocket.paste()). The tricky part is that our normal sending of keys also calls:

      evt.preventDefault();
      evt.stopImmediatePropagation();

That will probably stop the clipboard events from being fired (the keyboard processing fires first). If, so then we may need to bypass this processing for the COPY/CUT key sequences. Note that these should NOT be sent for ChUI.

4. The use of p2j.clipboard_helpers.getSelectedText() is broken in GUI. It is called from p2j.clipboard.prepareCopy(). Today, we cannot obtain the selected text from the client so this code sends a request to the server. The server then queries the currently focused widget and sends any selected text back to the client. The p2j.socket.onmessage() then calls p2j.clipboard.writeClipboard(). The problem is that we can't synchronously wait for the selected text to be sent back. Javascript is single threaded and cannot be paused or blocked. The normal approach to solve such issues is to split the deferred processing into a callback that executes when the data arrives. We have done this, but it doesn't always work because on chrome, firefox and safari (everything but IE) the clipboard cannot be accessed outside of the clipboard event. So, we can't copy to the clipboard during p2j.clipboard.writeClipboard() because it is not executing within the clipboard event. And we need to disable the normal clipboard event processing in this case because it will just copy garbage to the clipboard. This is a serious problem because it means that COPY/CUT in GUI simple do NOT WORK right now (except on IE). On the server side, see GuiWebSocket.processBinaryMessage() and the processing in there for MSG_CLIPBOARD_PREPARE.

5. Server driven system clipboard PASTE processing that is not generated by a user event will not work except for IE. This includes the client's response to MSG_READ_CLIPBOARD which is handled in p2j.clipboard.sendClipboardContents().

6. Termination and timeout processing needs to be added to GuiWebDriver.readClipboard().

7. We may need to implement GuiWebDriver.copyToClipboard(), if we end up using the normal clipboard processing events as part of the GUI COPY/CUT implementation. See FillInGuiImpl.processKeyEvent() and search on KA_COPY. At this point, that code is supposed to be empty because we request the selected text directly. But if the current approach cannot be made to work, then this code may need changes.

8. We definitely will need to implement GuiWebDriver.copyToClipboard() to support server-driven COPY/CUT that is not initiated by a user event. Only IE will work today. This is the analog to item 5 above, but it can probably be implemented with the same solution as is found for p2j.clipboard.writeClipboard() (see item 4 above).

I did do some initial testing of the idea 1 from note 722 (saving off the e.clipboardData instance during the clipboard event and then trying to use it outside of the clipboard event. I could not get it to work. Please see the attached standalone code to see what I tried.

#724 Updated by Greg Shah almost 9 years ago

Rebased task branch 1811o from P2J trunk revision 10895.

#725 Updated by Greg Shah almost 9 years ago

I am working on the new window and new child window support for the web client. In the current implementation, we use a JFrame for any normal 4GL window and a JWindow for alert-boxes/dialog-boxes.

In the web client, each window (regular window or alert/dialog) will be its own canvas. The current GUI client is only working with AWT/Swing so far. Considering that the two window types have very different implementations in Swing, I am worried that the current client replies upon implicit behavior differences between the two window types.

Here is the list of differences between JFrame and JWindow, as I know them:

  • Decorations (titlebar, borders, min/max/restore/close buttons, system icon) - JFrame has decorations by default, which can be eliminated using setUndecorated(true). JWindow never has decorations. We disable decorations in our use of JFrame, so there should be no dependency there.
  • User-Driven Moving/Sizing - JFrame can be directly sized and moved by the end-user, JWindow cannot. This moving/sizing is enabled by the window decorations. When decorations are disabled, the user-driven moving and sizing is no longer possible, so there should be no dependency there.
  • Menubar Support - JFrame has built-in support for adding a JMenuBar that exists outside of the content pane. JWindow has no such JMenuBar support. We don't use JMenuBar so there is no dependency there.
  • Frame/JFrame Interfaces - the JWindow does not have the Frame or the JFrame in its inheritance hierarchy. Any behavior that can only be called on Frame/JFrame will be missing from JWindow. The JMenuBar support is one example. I don't know if there are other cases that we are exploiting. On a positive note: if we do exploit some frame-specific features, then it would be done in the Swing-specific driver layer and thus the feature should not be hard to find.
  • Taskbar Integration - The JFrame has natural support for the desktop system's taskbar. Supposedly, the JWindow doesn't have this, but in my tests it does appear (at least in the Lubuntu task bar). See below for some child window specifics.
  • Top-Level versus Owned (Child Window) - A JFrame is designed to be a top-level window for an application. The JWindow is designed to be implemented with an owning window (it can only be set at construction and without it, the JWindow may not be very useful). A JFrame is suitable for the role of an owner window for a JWindow. This owner design for JWindow leads to a range of "hidden" behavior:
    • JWindow is not focusable if the owner is not set.
    • JWindow is not focusable if the owner is set but the owner window is not showing on the screen.
    • A child window will always be above the owner window in z-order.
    • A child window can be made visible or invisible independently of the owner window, but if the owner window is not visible then the child window will not be visible.
    • Minimize of the owning window will minimize the child window.
    • Restore of the owning window will restore the child window if it was visible when the owning window was minimized.
    • Desktop task switching will treat the set of owner window and child windows as a single entity when switched to via a click on the icon in the task bar or a desktop facility like ALT-TAB task switching. This means that all windows are restored as one.
    • The child window adds an icon to the taskbar, but cannot be manipulated on its own. I suspect this "feature" is probably not compatible with the 4GL implementation and I have not checked if our implementation disables this in some way.

It seems to me that we may be depending on the visibility/z-order/task-switching/focus dependencies inside Swing. I'm interested in feedback in regard to the ways in which these dependencies may manifest. To resolve any such dependencies, we must duplicate these implicit features in our web client driver OR we must turn the implicit dependencies into explicit calls to the driver APIs (possibly new calls). I'm open to ideas here.

A useful testcase that shows some of the owner/child window dependencies at work:

import javax.swing.*;
import java.awt.event.*;
import java.awt.*;

public class ParentChildFrames
{
   public static void main(String[] args)
   {
      SwingUtilities.invokeLater(new Runnable()
      {
         public void run()
         {
            JFrame parentFrame = new JFrame("Parent");
            parentFrame.setSize(250, 250);
            parentFrame.setLocation(150, 150);
            // parentFrame.setUndecorated(true);

            final JWindow childFrame = new JWindow(parentFrame);
            childFrame.setSize(250, 250);
            childFrame.setLocation(550, 150);
            childFrame.getContentPane().setBackground(Color.RED);

            final JCheckBox childVisible = new JCheckBox("Child Window Visibility");
            parentFrame.add(childVisible);

            childVisible.addItemListener(new ItemListener()
            {
               public void itemStateChanged(ItemEvent e)
               {
                  childFrame.setVisible(childVisible.isSelected());
               }          
            });

            childVisible.setSelected(true);

            parentFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            parentFrame.setVisible(true);
         }
      });
   }
}

#726 Updated by Greg Shah almost 9 years ago

Some questions related to window creation/destruction:

1. What is the purpose of the title passed to the JWindow? I guess the only thing it might do for us is to set the name of the program in the desktop taskbar?

2. Our current registration/deregistration approach simply stores/removes the EmulatedWindowState instance in a map in GuiPrimitivesImpl. It seems to me that our AWT/Swing resources may need to be cleaned up besides just cleaning up this map. If we do have such AWT/Swing cleanup, I'm not sure where it is. Ideas?

#727 Updated by Hynek Cihlar almost 9 years ago

Greg Shah wrote:

Some questions related to window creation/destruction:

1. What is the purpose of the title passed to the JWindow? I guess the only thing it might do for us is to set the name of the program in the desktop taskbar?

Yes. The title is for things like taskbar, Task Manager, etc.

2. Our current registration/deregistration approach simply stores/removes the EmulatedWindowState instance in a map in GuiPrimitivesImpl. It seems to me that our AWT/Swing resources may need to be cleaned up besides just cleaning up this map. If we do have such AWT/Swing cleanup, I'm not sure where it is. Ideas?

Indeed we are missing a call to dispose(). I think it should be added to SwingEmulatedWindow.quit(). Also quit() should be called on SwingEmulatedWindow.pane.

#728 Updated by Hynek Cihlar almost 9 years ago

ContentPane already contains a call to window.dispose() so it should be enough to make sure ContentPane.quit() is called.

#729 Updated by Greg Shah almost 9 years ago

Hynek Cihlar wrote:

ContentPane already contains a call to window.dispose() so it should be enough to make sure ContentPane.quit() is called.

I've added this to my changes in branch 1811o.

#730 Updated by Greg Shah almost 9 years ago

Attached are some samples I am using to get the drawing operations in Javascript to match the Java2D operations that are already implemented in the Swing GUI client. I am also attaching some example output.

As of this version, it supports line drawing, stroked/filled rectangle and stroked/filled rounded rectangle.

#731 Updated by Sergey Ivanovskiy almost 9 years ago

To "use strict"; directive in p2j.js module requires to declare p2j variable explicitly by
var p2j = (function()...)

#732 Updated by Greg Shah almost 9 years ago

Sergey Ivanovskiy wrote:

To "use strict"; directive in p2j.js module requires to declare p2j variable explicitly by
var p2j = (function()...)

Questions/Comments:

1. I like the idea, but I would like you to document (in this task history) more details about why this is a good idea AND your analysis/testing of why this is safe to do.

2. This is a useful change for more than just p2j.js, right?

3. Please create a task branch 1811p and make your proposed changes (for all .js files). Use that task branch to test your changes with the web client. Report your results here. Notify me here when you are ready for a code review.

#733 Updated by Sergey Ivanovskiy almost 9 years ago

This article "JavaScript’s Strict Mode and Why You Should Use It" http://cjihrig.com/blog/javascripts-strict-mode-and-why-you-should-use-it/ gives analysis of why "use strict" is safe to do.

#734 Updated by Sergey Ivanovskiy almost 9 years ago

The incomplete description of web client and simple server architecture is presented here in order to save time of developers:
Starting simple server by ServerDriver.main creates an instance of StandardServer according to bootstrap parameters and invokes its bootstrap method,
public void bootstrap(BootstrapConfig config), where all application services are registered and on hookInitialize it is created web server and the target requests handler WebHandler. From browser client side GET requests are handled by webHandler according to target resource path /gui or /chui . If it is a request for /chui, then ChuiWebDriver is created and as a response the browser displays the login page. A user posts own credentials and WebHandler (POST request handler) creates WebClientSpawner, registers remote service interface Spawner and then invokes spawn method of WebClientSpawner, public void spawn(String userName, String password, Map<String, String> envVars). Here is created a separate process with user's OS identity and this new process gets a target bootstrap configuration and call ClientCore start method. ClientCore gets a temporary OS user in its bootstrap configuration and gets Spawner proxy after created Session with StandardServer using this temporary user. Via Spawner
ClientCore gets TemporaryClientTask and its doWork method return the target ScreenDriver and starts an embedded web server with the target resource handler. And then WebHandler (POST request handler) waits until it is notified that the client's embedded web server is ready. On ready it redirects post request to the client's embedded web server that is responsible for web client's UI drawings and communications via web sockets.

#735 Updated by Sergey Ivanovskiy almost 9 years ago

1811p task branch is created on devsrv01 with help of create_task_branch.sh
Greg, what are tests we should perform in order to prove that web client works properly? Is it manual testing?
Also, the article "JavaScript’s Strict Mode and Why You Should Use It" is attached.

#736 Updated by Greg Shah almost 9 years ago

  • Status changed from New to WIP

1811p task branch is created

Please note which trunk revision the branch was created from.

Greg, what are tests we should perform in order to prove that web client works properly? Is it manual testing?

Yes.

It would be useful to check to see if MAJIC is working. You can use your devsrv01 environment for that (each user's environment is already mostly configured for access via the web client):

1. Follow the standard instructions in timco.html to setup your testing environment and to run an initial conversion/build of MAJIC.

2. Create the spawner directory:

mkdir ~/testing/spawner/

3. Ensure that the spawner is installed into that directory:

cd ~/testing/majic/p2j/
ant jar native -Dpost.build=yes -Dspawn.install.folder=~/testing/spawner -Dspawn.build.script=/opt/scripts/postbuild.sh

LE: I have added the -Dspawn.build.script=/opt/scripts/postbuild.sh to this command to match a new ant build option that will bypass the sudo rights problem documented in note 771 below.

4. Determine your admin port, web client port and GSO instance number:

port_mapping.sh <userid> gso_web_port
port_mapping.sh <userid> gso_admin_port
port_mapping.sh <userid> gso_instance

For me, <userid> is ges and the results are 10433, 7433 and 0 respectively.

5. Setup port forwarding on your local workstation using "localhost" for the admin and web client ports, i.e.:

ssh localhost -p <port_to_devsrv01> -l <userid> -N -f -L<local_admin_port>:localhost:<gso_admin_port> -L<local_web_client_port>:localhost:<gso_web_port>

On the standard workstation, <port_to_devsrv01> is 2224. Substitute the <userid> and the <gso_admin_port>/<gso_web_port> as found in the previous step.

6. Start the MAJIC server on devsrv01:

cd ~/testing/majic/run/server/
./server.sh -i<gso_instance>

For me, the instance number 0 was found at the previous step (using port_mapping.sh).

7. Start a native client on devsrv01, to confirm that the server is running and that NCURSES clients are executing:

cd
~/testing/majic/run/client/client.sh -i<gso_instance>

Login to MAJIC using syman userid and password of test123. Traverse the menus and try out some screens.

8. On your local workstation, access the web client, using "localhost":

https://localhost:&lt;local_admin_port&gt;/chui

The first time in, you'll have to add a security exception. The first page requires your devsrv01 OS credentials. Once MAJIC appears, use the same login for MAJIC as above. Execute the same manual testing as you did for the native NCURSES client and compare the results.

#737 Updated by Sergey Ivanovskiy almost 9 years ago

1811p task branch is created. Branched 10898 revisions

#738 Updated by Greg Shah almost 9 years ago

Yesterday and today, I have worked on adding color support and 3D rectangle drawing. As part of that I also resolved most of the pixel by pixel differences between the Java2D output and my previous Javascript drawing primitives.

The following issues still exist:

1. The Javascript line drawing is done such that the line with 1-pixel width is drawn centered over the infinitely thin line between two points. This means that half a pixel is drawn on one side of the line and the other half is drawn on the other side. This combined with some color interpolation (or compositing, I'm not sure) is causing the drawLine() Javascript version to be thicker and smoother than the Java2D version.

2. This same centered over a line issue occurs for paths. So, when you draw a rectangle using a closed path, half a pixel of the stroked line is drawn outside the path. I solved this issue by clipping to the closed path, which then only draws the inside half of the stroked path. This got the dimensions to be correct, BUT the color interpolation (or compositing, I'm not sure) is causing a lighter color to be used where Java2D uses the exact colors specified.

3. The corners of the rounded rectangles are not exactly the same curves. I will try non-quadratic curves next to see if I can match better. Again, there is a color interpolation (or compositing, I'm not sure) issue going on where some lighter pixels are included in the corners in javascript where the Java2D version is purely the requested color.

I tried a couple of different settings for compositing: globalCompositeOperation = "source-atop"; and globalCompositeOperation = "copy";, but neither made any difference. I'll explore this further tomorrow.

Here is the image of the current results:

#739 Updated by Greg Shah almost 9 years ago

I forgot the latest version of the Java2D samples.

#740 Updated by Eric Faulhaber almost 9 years ago

Greg Shah wrote:

...color interpolation (or compositing, I'm not sure)...

anti-aliasing?

#741 Updated by Greg Shah almost 9 years ago

Yes, anti-aliasing is probably a more accurate description than color interpolation.

#742 Updated by Hynek Cihlar almost 9 years ago

Btw. the black-filled rounded Java2D rectangles in canvas_drawing_output_comparison_20150723.jpg don't seem to be symmetric across the X axis. It looks like they are cut off by 1 pixel.

#743 Updated by Greg Shah almost 9 years ago

Yes, I noticed that. But I am guessing that is something unique to the Java2D implementation and is not needed for actual Windows drawing compatibility. :)

So for now I'm not duplicating that "feature".

#744 Updated by Greg Shah almost 9 years ago

I added major tick marks every 10 pixels and minor tick marks every 2 pixels. Attached is a close-up of the results. It shows the strange "centered on an invisible line" behavior of javascript canvas, where Java2D treats the pixels as "cells". For a line defined by points (10, 10) and (15, 10):

  • Javascript canvas draws the line starting from (9.5, 10.5) through (15.5, 10.5).
  • Java2D does what you expect and draws the line starting from (10, 10) through (15, 10).

Here is a zoomed in portion:

The full output:

Some useful references:

http://stackoverflow.com/questions/4261090/html5-canvas-and-anti-aliasing
http://stackoverflow.com/questions/195262/can-i-turn-off-antialiasing-on-an-html-canvas-element
http://www.mobtowers.com/html5-canvas-crisp-lines-every-time/

SOME but NOT ALL of the anti-aliasing effects can be eliminated by using the ctx.translate(0.5, 0.5) such that all drawing is done in the coordinate space that matches Java2D. In other words, using pixels as cells. There is still some effect, but it is much better:

And the full result:

I'm still working to find a solution to the rest of the anti-aliasing problem. The remaining issue manifests as a lightening of the colors...

#745 Updated by Greg Shah almost 9 years ago

I've tried a variety of compositing approaches and other means, to disable or bypass the anti-aliasing.

In the end, I have dropped back to manually drawing lines and overdrawing the rectangle outlines. This gets results almost 100% identical with the Java2D version, at the cost of implementing 2 helper functions to do pixel drawing. There is no perceptible lag from a user perspective, although we will have to see how it works in P2J.

We are not copying pixel data around. Rather, we just force specific pixels to a specific color value as needed. For rectangles (and for the upcoming polygon), we still use the canvas API to setup paths. Those paths are used for both clipping and filling. But we then overwrite the stroked external border with our pixel-drawing and that allows us to bypass the anti-aliasing "feature".

The core line drawing algorithm was invented in 1962 by Jack Bresenham of IBM. It is a very fast integer calculation that fully handles sloped/diagonal lines. This is probably the same approach as in Java2D because the results are identical. It is such a well known algorithm that this is highly likely. Some details:

https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm

Here is the result:

The only parts that aren't properly overdrawn are the rounded rectangle corners. I could implement a bezier curve version of the algorithm, but it would not necessarily follow the same path as the clipping region, which can only be determined using the path based canvas API. The fill operation is likewise dependent upon the canvas paths. Since our implementation was already slightly deviating from the Java2D rounded rectangle approach, I decided not to take this part of the solution further. Some tests will be needed to check to see if the Java2D approach needs any changes to match the 4GL result. Based on that we can determine if we need a better/different approach in Javascript. I'm going to defer that work until later.

#746 Updated by Sergey Ivanovskiy over 8 years ago

Greg, please review "use strict" changes. The changes are tested for a web chui client of the installed MAJIC with remote database on devsrv01.
All modified modules
src/com/goldencode/p2j/ui/client/chui/driver/web/res/p2j.clipboard_helpers.js
src/com/goldencode/p2j/ui/client/chui/driver/web/res/p2j.screen.js
src/com/goldencode/p2j/ui/client/driver/web/index.html
src/com/goldencode/p2j/ui/client/driver/web/res/p2j.clipboard.js
src/com/goldencode/p2j/ui/client/driver/web/res/p2j.js
src/com/goldencode/p2j/ui/client/driver/web/res/p2j.keyboard.js
src/com/goldencode/p2j/ui/client/driver/web/res/p2j.keymap.js
src/com/goldencode/p2j/ui/client/driver/web/res/p2j.socket.js
src/com/goldencode/p2j/ui/client/driver/web/res/p2j.sound.js
src/com/goldencode/p2j/ui/client/gui/driver/web/res/p2j.clipboard_helpers.js
src/com/goldencode/p2j/ui/client/gui/driver/web/res/p2j.screen.js
have the same structure that a module instance is created as a result of calling an anonymous constructor function. It is okay, but all modules are connected by p2j global object, not via interfaces. It is difficult for reading and it will be more simple if each module has named classes with private and public methods. For example,
function Keyboard() {
this.public = ...;
function private() {...};
}
and communicated with each others via interfaces but not via p2j global object.
This view is based on the article http://javascript.crockford.com/private.html, but I don't persist to do it.

#747 Updated by Greg Shah over 8 years ago

Fixed a bug in rawLine() and added polygon support. All of these changes are now integrated back into the 1811o branch with documentation and error checking. Next up is line stroke customization.

Latest output:

#748 Updated by Greg Shah over 8 years ago

Greg, please review "use strict" changes.

I don't see any changes checked into task branch 1811p. I realize you have posted a diffs file, but it is better to follow our standard development process. Please commit your changes and I will review them in the task branch.

The changes are tested for a web chui client of the installed MAJIC with remote database on devsrv01.

Do you mean that your changes worked properly using MAJIC in the web chui client?

This view is based on the article http://javascript.crockford.com/private.html, but I don't persist to do it.

I have been reading Crockford's "Javascript: The Good Parts". It is quite a useful guide to best practices.

have the same structure that a module instance is created as a result of calling an anonymous constructor function. It is okay, but all modules are connected by p2j global object, not via interfaces. It is difficult for reading and it will be more simple if each module has named classes with private and public methods. For example,
...
and communicated with each others via interfaces but not via p2j global object.

Generally, I am OK with your proposal. In fact, that is how I am implementing the Window "class" in gui/driver/web/res/p2j.screen.js. The primary problem that I see here is that these modules are meant to be singletons. Can you give me some idea of where you see the value in constructing multiple instances? Or can you suggest some examples of how you propose to change the connections to be by interface rather than the global p2j object?

For objects that are meant to be classes with multiple instances, then your proposal is surely the best idea. But for singletons it is less clear to me.

#749 Updated by Sergey Ivanovskiy over 8 years ago

I don't see any changes checked into task branch 1811p. I realize you have posted a diffs file, but it is better to follow our standard development process. Please commit your changes and I will review them in the task branch.

Now the changes are in 1811p and the branch is rebased from the trunc rev.10901

Do you mean that your changes worked properly using MAJIC in the web chui client?

I didn't check up all functions and only came through MAJIC screens according to handbook/timco.html instructions. Help functionality, menus and shortcuts work properly but if select Service Order Entry, then the target screen isn't displayed and the server logs

[07/28/2015 16:25:24 MSK] (com.goldencode.p2j.persist.Persistence:WARNING) [00000004:0000002C:syman-->local/majic/primary] error loading record
select customerOrder.id from CustomerOrderImpl as customerOrder where upper(rtrim(customerOrder.type, '
')) = 'S' and customerOrder.customerOrderNumber != 0 and customerOrder.customerOrderNumber = ?0 and indexOf(?1, upper(rtrim(customerOrder.status, '
'))) = 0

#750 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811p Revision 10902

My only question: wouldn't it be better to implement the "use strict" directive at the script level? Otherwise we have to remember to add it to every function definition.

Also: please put a history entry in each modified file.

#751 Updated by Greg Shah over 8 years ago

but if select Service Order Entry, then the target screen isn't displayed and the server logs

[07/28/2015 16:25:24 MSK] (com.goldencode.p2j.persist.Persistence:WARNING) [00000004:0000002C:syman-->local/majic/primary] error loading record
select customerOrder.id from CustomerOrderImpl as customerOrder where upper(rtrim(customerOrder.type, ' ')) = 'S' and customerOrder.customerOrderNumber != 0 and customerOrder.customerOrderNumber = ?0 and indexOf(?1, upper(rtrim(customerOrder.status, ' '))) = 0

Hmmm. This should not be an error that is due to the javascript changes, since it is about database record retrieval problems. Please test with the NCURSES client. You should see the same issue.

It is an issue we need to resolve but it is most likely a problem with your testing environment.

#752 Updated by Greg Shah over 8 years ago

Sergey: In addition to the "use strict" changes you are working on, I'd like you to look into the following problem.

I believe that the current ChUI Web client (in the P2J trunk) may have its clipboard support in a broken state. This may be a result of my previous refactoring work in revision 10867.

The ChUI client clipboard support is not integral with the 4GL functionality. It is purely a convenience for the user. The "select screen text and copy" feature replaces the kind of screen copying capability that exists in most terminal software. Likewise, the paste functionality eliminates the need for the user to type in long sequences of text they can copy from elsewhere. As you know, the core ChUI widgets themselves don't support copy/paste. So these features are just "layered on top". In the GUI web client it is different (and it is not fully working there, but that is not something to work on now).

Please test the "select and copy" feature (to see if the selection processing works and if the screen data is put into the system clipboard.

Also please test the paste function (to see if you can input system clipboard contents to the ChUI application).

I recommend testing in revision 10866 first. It should work there and you will see what to expect. Then test in 10867 to see if that works. If it does, then please check the latest revision of the trunk. Report the results here.

If anything is broken, please fix it as part of your work on task branch 1811p.

#753 Updated by Sergey Ivanovskiy over 8 years ago

Code Review Task Branch 1811p Revision 10902
My only question: wouldn't it be better to implement the "use strict" directive at the script level? Otherwise we have to remember to add it to every function definition.

"use strict" defined in each module's function gives possibility to work with an external non-strict jscript code in our strict code. I think it needs only if there is some non strict code that can be called within strict function or given as a parameter to the strict function. There are 8 modules: p2j, screen, keyboard, keymap, socket, sound, clipboard and clipboard_helper.

#754 Updated by Sergey Ivanovskiy over 8 years ago

Hmmm. This should not be an error that is due to the javascript changes, since it is about database record retrieval problems. Please test with the NCURSES client. You should see the same issue.

It is an issue we need to resolve but it is most likely a problem with your testing environment.

NCURSES client produces the same error on selection of Service Order Entry

[07/28/2015 18:09:24 MSK] (org.hibernate.engine.jdbc.spi.SqlExceptionHelper:ERROR) ERROR: function indexof(character varying, text) does not exist
Hint: No function matches the given name and argument types. You might need to add explicit type casts.
Position: 207
[07/28/2015 18:09:24 MSK] (com.goldencode.p2j.persist.Persistence:WARNING) [00000001:00000020:syman-->local/majic/primary] error loading record
select customerOrder.id from CustomerOrderImpl as customerOrder where upper(rtrim(customerOrder.type, '
')) = 'S' and customerOrder.customerOrderNumber != 0 and customerOrder.customerOrderNumber = ?0 and indexOf(?1, upper(rtrim(customerOrder.status, '
'))) = 0

But Inventory works properly. Please, would you help me with MAJIC configuration? The server at the start logs

[07/28/2015 17:47:24 MSK] (SecurityManager:INFO) {main} Loaded 25 ACLs for resource type <net>
[07/28/2015 17:47:24 MSK] (SecurityManager:INFO) {main} Audit decisions: 1, subjects: 0, resources 0
[07/28/2015 17:47:24 MSK] (SecurityManager:INFO) {00000000:00000001:gso} No exported entry points defined in the P2J directory
[07/28/2015 17:47:25 MSK] (com.goldencode.p2j.main.StandardServer:WARNING) Unable to initialize runtime conversion pool; if dynamic database features are not in use, this warning can be ignored safely (otherwise restart server with FINE logging for additional information)
[07/28/2015 17:47:25 MSK] (com.goldencode.p2j.classloader.MultiClassLoader:SEVERE) Could not find file for jar ../../customer_libs/managed/jayatana.jar!
[07/28/2015 17:47:25 MSK] (com.goldencode.p2j.classloader.MultiClassLoader:SEVERE) Could not find file for jar ../../customer_libs/managed/jayatana.jar!
[07/28/2015 17:47:25 MSK] (StandardServer.loadStartup:SEVERE) {00000000:00000001:gso} failed to load aero.timco.oasis.remote.p2j.OasisServerHookRegistrar
[07/28/2015 17:47:25 MSK] (Scheduler:WARNING) {00000000:0000001B:gso} No scheduler configured.
[07/28/2015 17:47:25 MSK] (com.goldencode.p2j.persist.DatabaseManager:INFO) Using H2 database version 1.3.169 (2012-09-09)
[07/28/2015 17:47:26 MSK] (com.goldencode.p2j.persist.meta.MetadataManager:INFO) Metadata server was not started; no metadata in use
[07/28/2015 17:55:20 MSK] (com.goldencode.p2j.persist.Persistence:INFO) [00000000:00000001:gso-->local/majic/primary] lock manager initialized [com.goldencode.p2j.persist.lock.InMemoryLockManager]
[07/28/2015 17:55:20 MSK] (com.goldencode.p2j.persist.id.IdentityPool:INFO) Identity pool initialized for local/majic/primary target_size = 10000 scan_threshold = 1000 initial_scan_interval = 3600 maximum_scan_interval = 86400 scan_range = 100000 scan_delay = 60 scan_method = JDBC
[07/28/2015 17:55:21 MSK] (com.goldencode.p2j.persist.Persistence:INFO) [00000000:00000001:gso-->local/majic/primary] identity manager initialized [com.goldencode.p2j.persist.id.SequenceIdentityManager]
[07/28/2015 17:58:37 MSK] (SecurityManager:INFO) {00000000:0000001C:gso} Loaded 1 CA certificates, 0 objects ignored
[07/28/2015 17:58:37 MSK] (SecurityManager:INFO) {00000000:0000001C:gso} Loaded 24 peer certificates, 0 objects ignored
[07/28/2015 17:58:37 MSK] (SecurityManager:WARNING) {00000000:0000001C:gso} No private keys defined in the P2J directory
[07/28/2015 17:58:37 MSK] (SecurityManager:INFO) {00000000:0000001C:gso} Loaded auth-mode object
[07/28/2015 17:58:37 MSK] (SecurityManager:INFO) {00000000:0000001C:gso} Loaded 50 groups, 0 objects ignored
[07/28/2015 17:58:37 MSK] (SecurityManager:INFO) {00000000:0000001C:gso} Loaded 24 processes, 0 objects ignored
[07/28/2015 17:58:38 MSK] (SecurityManager:INFO) {00000000:0000001C:gso} Loaded 862 users, 0 objects ignored
[07/28/2015 17:58:38 MSK] (SecurityManager:INFO) {00000000:0000001C:gso} Loaded password aging object
[07/28/2015 17:58:38 MSK] (SecurityManager:INFO) {00000000:0000001C:gso} Loaded 6 resource plugins, 0 failed
[07/28/2015 17:58:38 MSK] (SecurityManager:INFO) {00000000:0000001C:gso} Loaded 1685 ACLs for resource type <majic-function>
[07/28/2015 17:58:38 MSK] (SecurityManager:INFO) {00000000:0000001C:gso} Loaded 9 ACLs for resource type <system>
[07/28/2015 17:58:38 MSK] (SecurityManager:INFO) {00000000:0000001C:gso} Loaded 4 ACLs for resource type <majic-action>
[07/28/2015 17:58:38 MSK] (SecurityManager:INFO) {00000000:0000001C:gso} Loaded 6 ACLs for resource type <directory>
[07/28/2015 17:58:38 MSK] (SecurityManager:INFO) {00000000:0000001C:gso} Loaded 14 ACLs for resource type <admin>
[07/28/2015 17:58:38 MSK] (SecurityManager:INFO) {00000000:0000001C:gso} Loaded 25 ACLs for resource type <net>
[07/28/2015 17:58:38 MSK] (SecurityManager:INFO) {00000000:0000001C:gso} Audit decisions: 1, subjects: 0, resources 0
[07/28/2015 17:58:38 MSK] (SecurityManager:WARNING) {00000000:0000001C:gso} Security Cache generation 1 created.
[07/28/2015 17:58:38 MSK] (SessionManager.listen():WARNING) {00000000:00000001:gso} INSECURE sockets in use!
[07/28/2015 17:58:38 MSK] (SessionManager.listen():INFO) {00000000:00000001:gso} Server ready

#755 Updated by Greg Shah over 8 years ago

failed to load aero.timco.oasis.remote.p2j.OasisServerHookRegistrar

This is not a cause of the problem. This is just some missing customer-specific code that does not get involved in normal usage of MAJIC. It is also not used during our regression testing.

#756 Updated by Greg Shah over 8 years ago

"use strict" defined in each module's function gives possibility to work with an external non-strict jscript code in our strict code.

True. But do we want to allow usage of non-strict code?

And if we do find some non-strict code that we need to use, we can make the change for only the script(s) that need such access.

As of now, I know of no such needs.

Let's implement at the script level for now.

#757 Updated by Eric Faulhaber over 8 years ago

Sergey Ivanovskiy wrote:

_[07/28/2015 18:09:24 MSK] (org.hibernate.engine.jdbc.spi.SqlExceptionHelper:ERROR) ERROR: function indexof(character varying, text) does not exist
Hint: No function matches the given name and argument types. You might need to add explicit type casts.
Position: 207
[07/28/2015 18:09:24 MSK] (com.goldencode.p2j.persist.Persistence:WARNING) [00000001:00000020:syman-->local/majic/primary] error loading record
select customerOrder.id from CustomerOrderImpl as customerOrder where upper(rtrim(customerOrder.type, '
')) = 'S' and customerOrder.customerOrderNumber != 0 and customerOrder.customerOrderNumber = ?0 and indexOf(?1, upper(rtrim(customerOrder.status, '
'))) = 0

It appears PL/Java is not installed correctly. This is necessary for (database) server-side execution of some Progress-compatible built-in functions within database queries, which are implemented in Java.

Sorry, the books are a bit out of date on this point. Please see #2299, note 1. There you will find a more recent section on building and installing a Java 7 version of PL/Java.

The reason some parts of your Majic installation work and others don't is that not all queries use these built-in functions. The ones that don't will work normally. The ones that do will break as above.

#758 Updated by Greg Shah over 8 years ago

He is using a devsrv01 database instance. We shouldn't need to build pljava from scratch there.

#759 Updated by Eric Faulhaber over 8 years ago

Ah, I thought this was a local installation of the database. Still, there's something wrong with the built-in function.

ERROR: function indexof(character varying, text) does not exist

That's true, that function doesn't (and shouldn't) exist. The signature is indexof(text, text). It's recognizing the query substitution parameter $1 in

...and indexOf(?1, upper(rtrim(customerOrder.status, E' \t\n\r'))) = 0

as varchar instead of text for some reason, so it's complaining about needing a cast. I'm not sure why we're seeing this here and nowhere else. Something may have gone wrong with the last database restore.

#760 Updated by Constantin Asofiei over 8 years ago

Sergey, how did you start the server on devsrv01? via server.sh or via run_regression.sh? p2jpl.jar should be automatically installed when the server-startup task is executed.

Also, if you are testing previous trunk revisions, I'm not sure on the compatibility between the existing converted MAJIC code/p2jpl.jar and that trunk revision. So, if you go back in time, you need to reconvert and restore the DBs, so that everything is in sync with that P2J revision.

#761 Updated by Eric Faulhaber over 8 years ago

The problem is that there is no working database, only templates:

                                               List of databases
            Name             |  Owner   | Encoding |     Collate     |      Ctype      |   Access privileges   
-----------------------------+----------+----------+-----------------+-----------------+-----------------------
 gso_20090909_1_14_4a_master | gc       | LATIN1   | en_US@p2j_basic | en_US@p2j_basic | 
 postgres                    | postgres | LATIN1   | en_US@p2j_basic | en_US@p2j_basic | 
 rfq_20090909_1_14_4a_master | gc       | LATIN1   | en_US@p2j_basic | en_US@p2j_basic | 
 template0                   | postgres | LATIN1   | en_US@p2j_basic | en_US@p2j_basic | =c/postgres          +
                             |          |          |                 |                 | postgres=CTc/postgres
 template1                   | postgres | LATIN1   | en_US@p2j_basic | en_US@p2j_basic | =c/postgres          +
                             |          |          |                 |                 | postgres=CTc/postgres

Sergey, you shouldn't configure the JDBC URL in the directory to use any database ending in the name "master". These are supposed to remain pristine templates and are only there to restore working instances. We always make a copy of one of these and use that copy. On devsrv01, this is done using run_regression.sh, which runs an ant script. One of the targets in that script is server-startup, on which many of the targets are dependent. That target makes a working copy of the databases you will need.

Constantin, since it looks like the master was used against a live system, it is no longer pristine and should be replaced. How is that done on devsrv01? BTW, based on the target matrix in timco.html, it looks like there is no separate target just to restore the database. Do you always have to start a local server (LE: meaning, local to devsrv01) in order to do this?

#762 Updated by Constantin Asofiei over 8 years ago

Eric Faulhaber wrote:

Constantin, since it looks like the master was used against a live system, it is no longer pristine and should be replaced. How is that done on devsrv01?

See note 190 in #2104

BTW, based on the target matrix in timco.html, it looks like there is no separate target just to restore the database. Do you always have to start a local server (LE: meaning, local to devsrv01) in order to do this?

Yes, only server-startup calls the DB restore script, for the staging DBs. Master DBs need to be maintained manually - they were assumed read-only...

#763 Updated by Sergey Ivanovskiy over 8 years ago

It was my fault that I used the template gso_20090909_1_14_4a_master and didn't setup a personal MAJIC database on devsrv01. It is a good lesson for me to read the documentation and to ask questions if something is unclear.

#764 Updated by Greg Shah over 8 years ago

In the Javascript canvas, the management of clipping and translation is limited. It provides those features, but it does so using a simple stack to maintain state.

The Javascript canvas API is actually implemented in the CanvasRenderingContext2D. This is very much like the Java2D idea of Graphics2D. You obtain this context object from a canvas element. The canvas element itself is part of the DOM and has very little functionality on its own. The entire drawing API is in the context object.

That object is long-lived. We will use it over and over, for the life of the canvas element. For our purposes a canvas element is the same as a 4GL window.

Within this object, the state of various drawing properties like the current font, current fill or stroke color, line drawing widths... are kept in a stack. This state also tracks the current clipping region and transforms (of which the simple coordinate system translate() is one).

The basic drawing properties can be easily assigned any value we choose. But clipping and transforms are designed as a cumulative value. For example, the clipping region can only ever be reduced (by intersecting with an additional closed path). Likewise, the transforms accumulate into an ever-growing, never-shrinking pipeline such that every transform is processed from front to back in sequence. So: translate(50, 50) followed by a translate(-50, -50) will cancel each other out BUT they never disappear. They stay in the pipeline and have to be processed over and over again, every time something is drawn.

All of this state can be managed using a simple stacking mechanism. save() can be called to push the current state onto the stack and restore() can be called to pop the top of stack and force the state to those values. Direct or indexed access to the stack is not provided. Only the push/pop is available.

There is one other "trick" that can be used to reset all state to the initial values. One can set the width/height of the canvas element itself and that does a kind of full reset. But it is expensive and you are encouraged to avoid it.

If the clipping and transforms usage was always paired up, then this simple stack approach could be used safely. For example, in AbstractGuiDriver we have this code:

   public void draw(NativePoint origin, NativeRectangle clip, Runnable core)
   {
      translate(origin.x, origin.y);

      if (clip != null)
      {
         setDrawingArea(clip);
      }

      try
      {
         core.run();
      }

      finally
      {
         if (clip != null)
         {
            setDrawingArea(null);
         }
         // restore context
         translate(-origin.x, -origin.y);
      }
   }

I can implement some kind of stack based management for this use pattern. The clipping usage generates a PaintPrimitives.CLIP followed by a PaintPrimitives.NO_CLIP. This matched pair can be mapped into a save() and restore() respectively. And nesting will work fine there too. The translate() is a bit tricky because it just generates a PaintPrimitives.TRANSLATE and I have to keep my own stack of translations and check the top of the stack. If the top of the stack is the inverse of the newly provided translation request, then I could do a restore(), otherwise I'd do a save(). That MAY work, but there could be some use cases that break that pattern (if a nested translate() ever matches the inverse transform, then the detection would mistakenly pop the state. It would be safer to add a PaintPrimitives.REVERSE_TRANSLATE so that I can know exactly when to pop. Anyway, I can make all that work.

I have a problem in the case where there is no matched pairing of reversible transforms. In AbstractGuiDriver we have this code:

   public void drawFromOrigin(NativePoint origin, NativeRectangle clip, Runnable core)
   {
      NativePoint save = new NativePoint(this.translatedOrigin);
      translate(-save.x, -save.y);

      try
      {
         draw(origin, clip, core);
      }

      finally
      {
         // move current translated back to screen (0,0)
         translate(-this.translatedOrigin.x, -this.translatedOrigin.y);   <----- I'm concerned about this

         // translate to the saved origin
         translate(save.x, save.y);
      }
   }

This case has an extra translate() in the middle. And I'm not clear why it is there. If I understand correctly, the first translate(-save.x, -save.y) will modify this.translateOrigin to be (0, 0). This means the 2nd translate(-this.translatedOrigin.x, -this.translatedOrigin.y) is the same as translate(0, 0). It should not be needed at all. Then the 3rd translate(save.x, save.y) is the "reversing" translate.

I don't understand the need for the 2nd translate() and it will cause me some problems with the javascript implementation.

Hynek and Constantin, I'd like your thoughts, please.

#765 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

This case has an extra translate() in the middle. And I'm not clear why it is there. If I understand correctly, the first translate(-save.x, -save.y) will modify this.translateOrigin to be (0, 0).

This is correct

This means the 2nd translate(-this.translatedOrigin.x, -this.translatedOrigin.y) is the same as translate(0, 0). It should not be needed at all.

In the ideal case, after the inner draw has finished, the translatedOrigin will be back to (0,0). But in case translate calls are not paired properly, this ensures we are back to (0,0) so we can move it to the saved location. If we ensure that all explicit translate calls are paired properly, then this can be removed.

Then the 3rd translate(save.x, save.y) is the "reversing" translate.

Correct.

#766 Updated by Sergey Ivanovskiy over 8 years ago

I believe that the current ChUI Web client (in the P2J trunk) may have its clipboard support in a broken state. This may be a result of my previous refactoring work in revision 10867.
Please test the "select and copy" feature (to see if the selection processing works and if the screen data is put into the system clipboard.

Also please test the paste function (to see if you can input system clipboard contents to the ChUI application).

I recommend testing in revision 10866 first. It should work there and you will see what to expect. Then test in 10867 to see if that works. If it does, then please check the latest revision of the trunk. Report the results here.

If anything is broken, please fix it as part of your work on task branch 1811p.

Greg, please clarify how copy/paste should be performed in Web Chui client, the question is due to the fact that if we consider the frame with two editors and want to copy from one field to another, we have to select a target text from one. How the selection should work? Usually, we can use a mouse or a keyboard to select a text. But in revision 10866 mouse selection works partly because the selected text with lines becomes one line text after paste operation.

#767 Updated by Greg Shah over 8 years ago

How the selection should work? Usually, we can use a mouse or a keyboard to select a text. But in revision 10866 mouse selection works partly because the selected text with lines becomes one line text after paste operation.

At any time there is text the user wants to copy to the system clipboard, they would:

1. Move the mouse to the top left corner of the text to copy. The text can be anything in the ChUI web "terminal" screen.
2. Press down on the left mouse button to start the selection.
3. Without releasing the left mouse button, drag the mouse down and to the right until all the text is selected. The selection rectangle should be drawn as a highlight around the text.
4. Release the left mouse button. The selection rectangle remains and it is complete.
5. Use CTRL-C to copy the current selection to the system clipboard.
6. Use paste in some other application to confirm that the text is as expected.

There is no part of this operation where any processing shifts to the Java side. Everything is done in the browser and there is really just javascript logic involved.

Is this working as expected?

For paste, the user simply types CTRL-V while there is something in the system clipboard. The javascript code will read the system clipboard contents as text and it will send those contents to the server via the websocket, using a PASTE message type. On the server, each of the characters in the text of the message is inserted into whatever widget has the focus (in 4GL terms). Each character is inserted as if it was being typed by the user.

Is this working as described?

I really don't know if we insert line-ends when we copy more than one line. That may be an oversight on our part. Is that the point you are making?

#768 Updated by Sergey Ivanovskiy over 8 years ago

For rev 10866 the selection is working as expected the selection rectangle highlights the target text. And if there is a text selection, then Ctrl + C is working as expected. But if there is no selection, then Ctrl + C terminates the client's session and the client is redirected to login page. Yes, copy/paste of lines doesn't take into account line-ends. Ctrl+V is working as described.
For rev 10867 a mouse selection isn't working. The selection rectangle isn't displayed over text.

#769 Updated by Greg Shah over 8 years ago

But if there is no selection, then Ctrl + C terminates the client's session and the client is redirected to login page.

This is expected. 4GL users depend upon CTRL-C to generate a STOP condition. This is used very often (more often than the clipboard). In ChUI web client, we intentionally accept CTRL-C as a clipboard operation only when there is a selection. If there is a selection, we assume that the user must want to copy to the clipboard. Otherwise the CTRL-C is forwarded to the Java side as input, where it becomes a STOP condition.

Yes, copy/paste of lines doesn't take into account line-ends.

I guess the user would expect line ends to be there. Please fix this.

For rev 10867 a mouse selection isn't working. The selection rectangle isn't displayed over text.

Please fix this.

#770 Updated by Hynek Cihlar over 8 years ago

Greg Shah wrote:

I don't understand the need for the 2nd translate() and it will cause me some problems with the javascript implementation.

The 2nd translate() is needed in case the executed drawing code does a translation without proper cleanup.

I think exposing the translate() method is problematic. While I believe we don't have a use case that would require a long term origin translation, this method allows it. And so we need to take measures for functionality we don't need.

The solution I think is to keep the translate() method private and only expose the draw() operations that would handle cleanup properly. If a translation is required without a draw operation then I would create a new method executeTranslated(origin, code), again this method would handle proper translation cleanup.

#771 Updated by Sergey Ivanovskiy over 8 years ago

3. Ensure that the spawner is installed into that directory:

cd ~/testing/majic/p2j/
ant jar native -Dpost.build=yes -Dspawn.install.folder=~/testing/spawner

Greg, running this command isn't permitted for sbi@devsrv01 user:
native:
[exec] make -f /home/sbi/testing/majic/p2j/src/native/makefile.spawn
[exec] make1: Entering directory `/home/sbi/testing/majic/p2j/build/native'
[exec] rm -fv ../../build/native/spawn
[exec] removed ‘../../build/native/spawn’
[exec] gcc -g -Wall -O2 -s -DNDEBUG -I/usr/lib/jvm/java-7-openjdk-amd64/jre/../include -I/usr/lib/jvm/java-7-openjdk-amd64/jre/../include/linux -I/home/sbi/testing/majic/p2j/src/native /home/sbi/testing/majic/p2j/src/native/spawn.c -o ../../build/native/spawn -lm -lcrypt -ljvm
[sudo] password for sbi: [exec] sudo /home/sbi/testing/majic/p2j/src/native/postbuild.sh ~/testing/spawner ../../build/native/spawn

[exec] Sorry, user sbi is not allowed to execute '/home/sbi/testing/majic/p2j/src/native/postbuild.sh /home/sbi/testing/spawner ../../build/native/spawn' as root on devsrv01.internal.goldencode.com.
[exec] make[1]: * [postbuild] Error 1
[exec] make: *
[spawn] Error 2
[exec] make[1]: Leaving directory `/home/sbi/testing/majic/p2j/build/native'

BUILD FAILED
/home/sbi/testing/majic/p2j/build.xml:945: exec returned: 2

Total time: 32 seconds
But it doesn't need, if MAJIC is running on my local host. Now my staging database has been build by run_regression.sh server-startup following by run_regression.sh server-shutdown.

#772 Updated by Greg Shah over 8 years ago

running this command isn't permitted for sbi@devsrv01 user:

Good point. I have made some modifications to devsrv01 to allow an alternative approach.

1. Check out task branch 1811o and copy the build.xml and src/native/makefile.spawn@ into your own p2j branch. These have been modified to allow the override of the default postbuild.sh with one that comes from a common location.

2. Then execute the ant native command in step 3 of note 736 in #1811 like this:

ant jar native -Dpost.build=yes -Dspawn.install.folder=~/testing/spawner -Dspawn.build.script=/opt/scripts/postbuild.sh

You have been provided no-password sudo rights to execute /opt/scripts/postbuild.sh, which is a locked down copy of the current version in P2J. If that file changes later, we'll have to update the shared script, but it really should not change often.

Let me know if there are any problems.

#773 Updated by Sergey Ivanovskiy over 8 years ago

1811#756

"use strict" defined in each module's function gives possibility to work with an external non-strict jscript code in our strict code.

True. But do we want to allow usage of non-strict code?

And if we do find some non-strict code that we need to use, we can make the change for only the script(s) that need such access.

As of now, I know of no such needs.

Let's implement at the script level for now.

I performed some tests to define where to place "use strict" directive in order to use this mode properly.
Let us inject this code:
var hasStrict = (function() {
return this === undefined;
}());
if (hasStrict) {
console.log("Has \"use strict\" on init p2j module");
} else {
console.log("Has not \"use strict\" on init p2j module");
}
Then place "use strict" directive at the different levels in files index.html, p2j.js. Test results if hasStrict is true or not for each cases. Finally, it follows that we need to place "use strict" directive at the module definition level for each module. Please look at the committed code 1811p revision 10903 and run it to prove the reason.

#774 Updated by Greg Shah over 8 years ago

Please look at the committed code 1811p revision 10903 and run it to prove the reason.

I've looked at the code to see what you are doing. Please document the results of your testing here.

#775 Updated by Greg Shah over 8 years ago

end-lines are lost on the server side, it is unclear if screen.video (2-dim array, p2j.screen.js) should contain end-lines, does it depend on 4Gl widgets properties?

No, the javascript screen buffer has no knowledge of the 4GL widgets at all. It is just a two-dimensional array of character cells. We don't want there to be any knowledge of widget properties at the javascript level.

From your email:

1) Simple server starts a ClipboardBasic.execute (clipboard_basic.p) as its P2J entry. and then connect to server via web chui client by url(http://localhost:7443/chui)
2) "Copy from" editor displays in 3 lines "Hello\n4GL\nWorld!".
3) Select the text region
4) The result is that screen.video doesn't contain end-lines.
5) To check server response changed the client side code by adding end-lines p2j.screen.js is modified

     /**
       * Copy selected area into clipboard.
       *
       * @param {number} begRow start row
       * @param {number} endRow end row
       * @param {number} begCol start column
       * @param {number} endCol end column
       *
       * @returns {object} selected text.
       */
      me.copy = function(begRow, endRow, begCol, endCol) ....

6) Move focus to "Paste to" editor and press CRL+V
7) The result is that the "paste" function from p2j.clipboard.js gets copied message with end-lines (they were added in 5)
  /**
   * Send paste text down to server to be injected
   * into the keyboard queue.
   *
   * @param {type} text   text to send.
   * @returns {undefined}
   */
   function paste(text)

8) The response from server draws copied text in one line.

Why did you remove your 10902 p2j.screen.js changes to add the new lines (and also to fix the var defs in the loops)? It is hard to review your changes when I have to search specific revisions to see them. Unless there is a good reason, please let your changes accumulate in the branch so that the latest branch revision shows the results of all your work.

Overall, adding newlines to the copy function seems like a reasonable way to do this. I don't know offhand why these would be "eaten" on the server side.

#776 Updated by Sergey Ivanovskiy over 8 years ago

Overall, adding newlines to the copy function seems like a reasonable way to do this. I don't know offhand why these would be "eaten" on the server side.

Greg, please correct me, now web client supports browser side clipboard with help of hidden input field. If a user presses CTRL+C after selection, it saves the target selection on the browser side clipboard eating CTRL+C, server side doesn't get CTRL+C, the next CTRL+V invokes sending the selected text. Then this text is streaming to server client ThinClient with help of TypeAhead. Editor that has a focus getsa char by char. Here may be a problem, because public void processKeyEvent(KeyInput ke) of EditorImpl gets char '\n' with Keyboard.KA_CURSOR_DOWN actionCode and then the new line char presented in the target selection is eaten by EditorImpl logic. I think we should send from web client CTRL+V in order to change the behavior of the focused instance of EditorImpl.

#777 Updated by Greg Shah over 8 years ago

web client supports browser side clipboard with help of hidden input field

Yes.

If a user presses CTRL+C after selection, it saves the target selection on the browser side clipboard eating CTRL+C, server side doesn't get CTRL+C

Yes, except as part of the browser CTRL-C processing, clipboard events are generated and it copies the selection into the system clipboard. That way it can be used with other apps on the user's system.

the next CTRL+V invokes sending the selected text

Not exactly. It will read whatever data is in the system clipboard and send that to the server as a PASTE message. The contents of the system clipboard do not have to be sourced from a previous CTRL-C in our javascript client.

Then this text is streaming to server client ThinClient with help of TypeAhead. Editor that has a focus getsa char by char.

Yes.

Here may be a problem, because public void processKeyEvent(KeyInput ke) of EditorImpl gets char '\n' with Keyboard.KA_CURSOR_DOWN actionCode and then the new line char presented in the target selection is eaten by EditorImpl logic.

Interesting. Yes, that sounds like the issue.

I think we should send from web client CTRL+V in order to change the behavior of the focused instance of EditorImpl.

Do you suggest changing WebClientProtocol.injectKey() or WebClientProtocol.paste()?

#778 Updated by Constantin Asofiei over 8 years ago

Sergey Ivanovskiy wrote:

Overall, adding newlines to the copy function seems like a reasonable way to do this. I don't know offhand why these would be "eaten" on the server side.

Greg, please correct me, now web client supports browser side clipboard with help of hidden input field. If a user presses CTRL+C after selection, it saves the target selection on the browser side clipboard eating CTRL+C, server side doesn't get CTRL+C, the next CTRL+V invokes sending the selected text. Then this text is streaming to server client ThinClient with help of TypeAhead. Editor that has a focus getsa char by char. Here may be a problem, because public void processKeyEvent(KeyInput ke) of EditorImpl gets char '\n' with Keyboard.KA_CURSOR_DOWN actionCode and then the new line char presented in the target selection is eaten by EditorImpl logic. I think we should send from web client CTRL+V in order to change the behavior of the focused instance of EditorImpl.

I don't understand this one. Editor.processKeyEvent has this code to treat the KA_RETURN and KA_NEW_LINE cases:

      if (Keyboard.isPrintableKey(key) || 
          key == Keyboard.KA_RETURN    ||
          key == Keyboard.KA_NEW_LINE)
      {
         if (config.readOnly)
         {
            return;
         }

         ConfigHelper.setModified(config, true);

         char ch = Utils.toChar(key);

         if (key == Keyboard.KA_RETURN ||
             key == Keyboard.KA_NEW_LINE)
         {
            ch = LF;

            if (key == Keyboard.KA_NEW_LINE)
               cursorEndOfLine();
         }

         placeChar(ch);

         forceRedraw = true;
      }

Are you saying this code is bypassed when CTRL-C sends a \n character? I can't see how KA_CURSOR_DOWN is involved... Also, what revision are you working on? If you are working on an old revision, EDITOR has faults which are fixed in trunk rev 10896.

#779 Updated by Sergey Ivanovskiy over 8 years ago

I don't understand this one. Editor.processKeyEvent has this code to treat the KA_RETURN and KA_NEW_LINE cases:
[...]
Are you saying this code is bypassed when CTRL-C sends a \n character? I can't see how KA_CURSOR_DOWN is involved... Also, what revision are you working on? If you are working on an old revision, EDITOR has faults which are fixed in trunk rev 10896.

Constantin, CTRL+P invokes WebProtocolClient.onMessage with type WebClientProtocol.MSG_PASTE message that contains 0x0a, then TypeAhead.dequeue it to the KA_CURSOR_DOWN event type

private synchronized KeyInput dequeueEvent() {
KeyInput event = null;
if (!taBuf.isEmpty()) {
event = EventManager.eventFromKey((taBuf.get(0)).intValue());//here event 0x0a becomes KA_CURSOR_DOWN
taBuf.remove(0);
}
return event;
}

Constantin, Greg, thanks it helps me if instead of '\n' web client will send '\r' as EOL that is okay for EditorImpl and it is treated properly.

#780 Updated by Greg Shah over 8 years ago

it helps me if instead of '\n' web client will send '\r' as EOL that is okay for EditorImpl and it is treated properly.

Perfect, go ahead with that change.

#781 Updated by Greg Shah over 8 years ago

Also, please put in a comment to explain why \r is being used instead of \n.

#782 Updated by Greg Shah over 8 years ago

If a translation is required without a draw operation then I would create a new method executeTranslated(origin, code), again this method would handle proper translation cleanup.

This is a good idea. The only problem is that the use cases are not simple enough to make it easy to implement this way.

The usage locations:

ThinClient.processEventsWorker() - in GUI only and only if the widget has a parent, starts translation and reverses translation
ThinClient.widgetDrawingOn() - conditionally (if GUI) starts translation
ThinClient.widgetDrawingOff() - conditionally (if GUI) reverse translation
AbstractGuiDriver.draw() - starts translation and reverses translation
AbstractGuiDriver.drawFromOrigin() - starts translation, as an extra translate() in the middle and reverses translation, I believe the middle translate() is safe to remove
ScrollableListGuiImpl.draw() - starts translation and reverses translation
ScrollBarGuiImpl.drawScrollBarThumb() - starts translation and reverses translation

All the cases except for the ThinClient usage could easily fit into the translateExecute() model. Unfortunately, the ThinClient use cases are complex enough that it is not easy to refactor the code to fit.

For this reason, I am going with a push/pop model that should make it very clear that the start and reverse need to be paired.

#783 Updated by Sergey Ivanovskiy over 8 years ago

Greg, please review final changes for 1811p branch rev. 10904.

#784 Updated by Sergey Ivanovskiy over 8 years ago

Added all changes from rev 10901 trunc to rev 10904 of branch 1811p.

#785 Updated by Greg Shah over 8 years ago

Please document the results of your "use strict" testing here.

#786 Updated by Sergey Ivanovskiy over 8 years ago

Please document the results of your "use strict" testing here.

There are no possibilities to define "use strict" in one place in order to be applied to all loaded modules simultaneously. Web client loads 8 modules and external dojo loader library if a user logins successfully. Dojo loader waits until all resources are ready and then initializes p2j object.
1) if "use strict"; is applied to index.thml inside <script>"use strict";</script> then modules continue to work in no strict mode.
2) if "use strict"; is placed in or outer dojo ready function then there are no effects to application modules.
3) if "use strict"; is placed in p2j module, it applies only to p2j module.

#787 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811p Revision 10904

1. I still have not seen any testing results with my original idea of moving "use strict" outside of the functions. I prefer it at the "script" level. By this, I mean that it would be at the top of every .js file:

"use strict";

p2j.screen = (function()
{
   /** Screen object which will be returned from this initialization function. */
   var me = {};

instead of this:

p2j.screen = (function()
{
   "use strict";
   /** Screen object which will be returned from this initialization function. */
   var me = {};

I appreciate your other findings. I understand that this must be in each script file and cannot be specified globally. I'm fine with that. I just prefer to force all of our javascript code into strict mode by default, rather than having to remember to add it to every top-level function (error prone). If we do have to access non-strict code, it is easy enough to move/change the use strict directives later.

2. In p2j.clipboard.js copy() please only use 1 history entry for all the features in this update. The rule is 1 history enter for each commit. Just merge the text together.

3. I see your changes in p2j.clipboard_helpers.js to use canvas instead of this. Does that fix copy now?

4. Is there anything more to work on for the clipboard issues?

5. In p2j.keymap.js there is this change:

   /**
    * Key mapping object which will be returned from this initialization
    * function.
    */

where the code used to be this:

   /** Key mapping object which will be returned from this initialization function. */

The original form is preferred, since it is a data member comment we try to leave it in a single line so long as we don't go past the max line length (98 characters). Since it fits fine, it is preferred to leave it alone.

6. There is no real change to the gui/driver/web/res/p2j.clipboard_helpers.js. The "use strict"; is missing.

#788 Updated by Sergey Ivanovskiy over 8 years ago

Code Review Task Branch 1811p Revision 10904

All done accordingly

3. I see your changes in p2j.clipboard_helpers.js to use canvas instead of this. Does that fix copy now?

Yes, i have checked (used debug mode) rev10867 again to prove that coordinates are calculated incorrectly, simply zeros.

4. Is there anything more to work on for the clipboard issues?

No, I don't find any issues.
Greg, please review it rev 10905.

#789 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811p Revision 10905

It looks good.

Please rebase the update to the latest trunk revision. There should not be any conflicts. Post the notice here as per our standards.

Since the changes are only in the index.html and .js files, there is no way to exercise these changes using our normal regression testing environment. After the rebase, you merge to trunk and commit it there. Then post the merge notice here AND send the merge notification to the team. Let me know if you have any questions.

#790 Updated by Greg Shah over 8 years ago

Next task: I would like you to help me add image support to the GUI web client. You will be making changes in task branch 1811o, for which I am the primary owner. I am also making frequent changes to that branch and thus we will need to coordinate our commits.

To start, please review notes 652 and 653 above.

Then please review the PaintPrimitives.DRAW_IMAGE implementation in our GUI client. The easiest way is to search for all instances of DRAW_IMAGE. Make sure you are looking in branch 1811o. You will find 6 references in 5 files. Carefully review that implementation to get the idea of what we need to implement in GuiWebEmulatedWindow, GuiWebSocket and gui/driver/web/res/p2j.screen.js@. The idea is to follow the pattern of support starting with GuiWebEmulatedWindow.draw() as I have done for other drawing features.

As soon as you have merged to trunk, I will rebase 1811o so that we are working off the latest version.

Let me know what questions you have.

#791 Updated by Sergey Ivanovskiy over 8 years ago

Let me know what questions you have.

Greg, now 1811p has been rebased to the trunc 10905, do want to see in the trunc all history from 1811p? Can I merge these to trunc? Or do it in one commit?

#792 Updated by Sergey Ivanovskiy over 8 years ago

Now 1811p is branched from rev 10905 of trunc.

#793 Updated by Greg Shah over 8 years ago

The rebase looks fine.

do want to see in the trunc all history from 1811p

When you commit the change in the trunk, you must put in a proper message that describes the changes. Follow the documentation on the format (hint: there are requirements for referencing the Redmine issue number(s)).

The bzr merge will be committed in a single commit on the trunk. But the history will be there, if you follow the standard process.

Can I merge these to trunc?

Yes, please follow the specific process from our documentation. It is critical that you follow that process very carefully. Let me know if you have any questions.

#794 Updated by Sergey Ivanovskiy over 8 years ago

That is fine. 1811p branch is merged to the trunc with revno: 10906.

#795 Updated by Greg Shah over 8 years ago

Rebased task branch 1811o from P2J trunk revision 10906.

#796 Updated by Sergey Ivanovskiy over 8 years ago

Then please review the PaintPrimitives.DRAW_IMAGE implementation in our GUI client. The easiest way is to search for all instances of DRAW_IMAGE. Make sure you are looking in branch 1811o. You will find 6 references in 5 files. Carefully review that implementation to get the idea of what we need to implement in GuiWebEmulatedWindow, GuiWebSocket and gui/driver/web/res/p2j.screen.js@. The idea is to follow the pattern of support starting with GuiWebEmulatedWindow.draw() as I have done for other drawing features.

Greg, please correct if you don't like this design:
1)Draw an image on the server side in a memory (GuiWebEmulatedWindow,SwingEmulatedWindow)
2)Transfer binaries via web socket protocol to a web client (GuiWebSocket)
3)Get a binary image on the client side (p2j.socket.js), transform it to ImageData and draw on the canvas (p2j.screen.js)

#797 Updated by Greg Shah over 8 years ago

1)Draw an image on the server side in a memory (GuiWebEmulatedWindow,SwingEmulatedWindow)
2)Transfer binaries via web socket protocol to a web client (GuiWebSocket)
3)Get a binary image on the client side (p2j.socket.js), transform it to ImageData and draw on the canvas (p2j.screen.js)

I like this approach. I think it is exactly correct, since it allows us to support image types that are not supported in the browser.

Please add the following into your plan:

  • Each image should only be rendered and sent down to the browser one time, where it will be cached.
  • We should have a unique ID for each image.
  • When the Java side needs to render the image, it can just send the unique ID and the Javascript side can reuse the cached raw data.

#798 Updated by Sergey Ivanovskiy over 8 years ago

This very vague test (test.zip) yields several issues to be resolved. The fist one it needs to make an agreement between a server and a client about pixels format (for example, Remote Frame Buffer protocol), because drawing on a canvas uses https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/putImageData. The second issue it needs to specify a protocol to transfer the binary data between two parties. This protocol should describe an encoding schema and format for messages. The third issue is a resizing problem of the target canvas.
In this test a client requests an image by specifying a relative path to an image "./1.jpg", on its request server creates a web socket end point and loads an image from a relative path (for simplicity the project's directory should be in a launch classpath and the relative path should start from ./). The client gets an image and put it on a canvas. It is used a pixel format that encodes a pixel in 4 bytes according to its Red, Green, Blue and Alpha values in the given order. The width and height of an image are not send in this test and are fixed to 256X256. In test.zip there is a part of RFB implementation of vncj project by Tal Liron (http://emblemparade.net/projects/vncj/), but it is only for references and it will be removed in the real implementation.

#799 Updated by Sergey Ivanovskiy over 8 years ago

Fixed the test (canvas resizing, pixel format issue) and added ./red256x256.jpg, ./green256x256.jpg, ./blue256x256.jpg and ./yellow256x256.jpg images. Greg, how to test images using P2J, 4Gl function button.load-image()?

#800 Updated by Greg Shah over 8 years ago

how to test images using P2J, 4Gl function button.load-image()?

Until the GUI web client is further along, we can't use our replacement 4GL functionality for testing.

In a couple of weeks, we should be able to use the IMAGE widget and buttons with images to test it. For now, try to mock things up in your sample code so that you can just move the core functions over to our client code with minimal changes.

#801 Updated by Greg Shah over 8 years ago

Rebased task branch 1811o from P2J trunk revision 10916. The current revision in 1811o is now 10939.

#802 Updated by Sergey Ivanovskiy over 8 years ago

This is a draft format for DRAW_IMAGE sub-message based on RFB specification document(https://www.realvnc.com/docs/rfbproto.pdf, https://tools.ietf.org/html/rfc6143):
DRAW_IMAGE,//U8
xOffset,//S32, a horizontal position of the image top left corner
yOffset,//S32, a vertical position of the image top left corner
imageWidth,//U32
imageHeight,//U32
encoding_type,//S32, it can be Raw (RFB specification) or Md5Hash
pixel_format,// (RFB specification)[optional] if Md5Hash is used than it can be omitted
Md5Hash,//128 bits for image id constructed as a MD5 hash for image binaries.
Raw image encoding,// (RFB specification)[optional] if Md5Hash is used than it can be omitted

#803 Updated by Sergey Ivanovskiy over 8 years ago

Updated the test example, removed dependencies from rfb of vncj project. Needs to do pixel format serialization.

#804 Updated by Sergey Ivanovskiy over 8 years ago

Now it is implemented only 32 bits per a pixel RGB format, thus pixel_format isnot used in DRAW_IMAGE message:
DRAW_IMAGE,//U8
xOffset,//S32, a horizontal position of the image top left corner
yOffset,//S32, a vertical position of the image top left corner
imageWidth,//U32
imageHeight,//U32
encoding_type,//S32, it can be Raw (RFB specification) or Md5Hash
Md5Hash,//128 bits for image id constructed as a MD5 hash for image binaries.
Raw image encoding,// (RFB specification)[optional] if Md5Hash is used than it can be omitted.
Greg, do you permit to commit my changes in 1811o?

#805 Updated by Greg Shah over 8 years ago

do you permit to commit my changes in 1811o

Yes, go ahead.

Some other thoughts:

1. Is there a need to send a hash of the image? Why not a simple 32-bit integer as an ID? The images are not being edited once they are loaded. So after the first time it is pushed down to the client, all other times drawing needs to happen the ID can be sent instead.

2. Shouldn't the encoding_type always be raw? We can have a single byte that reports if there is image data included or not. The following data would have a length and then the raw bytes.

I guess I don't understand why we would add the increased complexity of the RFB protocol, when our protocol can be simpler.

#806 Updated by Sergey Ivanovskiy over 8 years ago

1. Is there a need to send a hash of the image? Why not a simple 32-bit integer as an ID? The images are not being edited once they are loaded. So after the first time it is pushed down to the client, all other times drawing needs to happen the ID can be sent instead.

I have selected md5 hash over an image hash because of it gives a universal id, but now agree with you that it doesn't need such complexity.

2. Shouldn't the encoding_type always be raw? We can have a single byte that reports if there is image data included or not. The following data would have a length and then the raw bytes.

Don't know it can be any encoding supported by a web client. For example, for drawing dynamic screens Hextile is useful, but I think for static images RRE is fitted well, the length of binaries images are less in compare to Raw and it permits rendering in a process of reading its binaries.
Please let me fix the code according to you thoughts and then I will commit the changes for a review and will notify about it.

#807 Updated by Sergey Ivanovskiy over 8 years ago

Greg, committed into 1811o, revision 10940, please review my changes. GuiWebDriver has method public NativeRectangle getDisplayBounds(). It is needed for VirtualScreen to set up its bounds correctly. But the current changes don't use it.

#808 Updated by Greg Shah over 8 years ago

Constantin: To see the history of the font work we have done so far, please review notes 687, 695, 705 - 718. Not much has been done yet.

We want to leverage the font metrics work we previously did for ChUI web client. The code is at the bottom of gui/driver/web/res/p2j.screen.js. I prefer not to use libraries if we can enhance our current work and get the job done. On the metrics issues, you will have to consider the cost of all the Java to Javascript round trips. I have not analyzed the "pattern" of calls to the metrics methods in the GuiDriver interface, but I suspect there may be some work here to get decent performance.

The SET_FONT, SET_FONT_STYLE and text drawing support (DRAW_STRING, DRAW_STRING_SCALED, DRAW_PARAGRAPH) are also the in gui/driver/web/res/p2j.screen.js. See the Window.prototype.draw() function. On the server side, the implementation will be in GuiWebEmulatedWindow and GuiWebSocket.

#809 Updated by Greg Shah over 8 years ago

Don't know it can be any encoding supported by a web client. For example, for drawing dynamic screens Hextile is useful, but I think for static images RRE is fitted well, the length of binaries images are less in compare to Raw and it permits rendering in a process of reading its binaries.

You are definitely thinking far ahead on this task. That is really good.

My initial idea was to implement a simple raw transfer of the bytes (4 bytes per pixel), no compression. Most images will be small (icon size) and since they are sent only once and never updated, I thought that adding compression and/or more complex management algorithms may not make much difference in the client's real performance.

#810 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811o Revision 10940

This is a very good start. I have some feedback. Much of the feedback is about meeting the coding standards. This is common in early code reviews, so don't worry. Just try to remember the things mentioned so that they don't have to be mentioned again. There is also some feedback that is more functional. But it really is quite a good start.

1. You made a very good decision to leverage AWT to render the different image formats (on input, like .ico, .bmp...) in memory on the Java side. However, with that said, you have your own custom rendering logic in DrawImageOp. Some of that logic duplicates code in SwingEmulatedWindow. And you are missing much of the rendering logic in how SwingEmulatedWindow handles the various features that can be supported (e.g. cropping, convert3D, transparent, stretchToFit...). Ultimately we must handle all these cases for the web client too. But it seems like we can implement most of this in common code, right? This would involve moving most of the image processing out of SwingEmulatedWindow and into some other common location. There seem to be 2 options:

  • We can move this logic into the level of the AbstractGuiDriver. This is simpler, but it makes our image processing dependent upon AWT for all future cases. Right now, we only have 2 kinds of GUI client. But someday we might want to implement a mobile GUI client (e.g. Android or iOS). In such a case we might need to make this more abstract again.
  • We can create common helper classes that live outside of both gui/driver/swing/ and outside of gui/driver/web/. This is a bit more work but is probably the better long term decision.

If possible, minimize/eliminate the AWT references from the external interface to whatever class or classes is created. In other words, it would be best if the specific user of these classes (e.g. GuiWebEmulatedWindow and/or GuiWebSocket) did not have to import AWT classes in order to use this.

2. The display size information is already available in GuiWebDriver.getDisplayBounds(), GuiWebDriver.getDisplayWorkArea(), GuiWebEmulatedWindow.getDisplayBounds() and GuiWebEmulatedWindow.getDisplayWorkArea(). I think the need for some of the VirtualScreen implementation can be eliminated. I also wonder if the rest of that class' functionality should be moved somewhere else and the VirtualScreen and VirtualScreenImpl classes removed.

3. Although we have not yet implemented this feature for GUI web client, in the ChUI web client it is possible for the user to reload the page manually. When this happens, the current web socket is disconnected and we try to silently reconnect with a new web socket instance. Soon, we will have to add this support to the GUI web client. Because of this web browser feature, we must design the web socket instance to never hold permanent state. The primary state in the web socket today is just the accumulated drawing ops for the current draw() call, which is transient so it is OK. For this reason, the caching of images should be implemented in GuiWebDriver and not in GuiWebSocket. We don't want it in GuiWebEmulatedWindow because we want the same image to be shared across the entire client instead of on a per-window basis. So GuiWebDriver seems to be the best place for the caching.

4. DrawImageOp should probably be eliminated. The caching functions should be moved to GuiWebDriver. The writeMessageInt32() is not needed because it is already in WebClientProtocol. The actual message processing should be directly in GuiWebSocket.

5. The Window.prototype.draw() changes in gui/driver/web/res/p2j.screen.js should follow this rule: the code in the DRAW_IMAGE portion of the switch statement should be focused on the message parsing. It should then call a Window method to do the actual image drawing work, passing the parsed data in as parameters.

6. I'd like to hear your ideas on how and when to remove images from the cache. This is both a Java and Javascript issue. We may want to retain things longer than their "normal" lifetime in the 4GL code, in case they are used again within some reasonable time. But we also don't want to keep things around forever.

7. Please minimize or (if possible) eliminate the AWT references in gui/driver/web/. For example, can we eliminate the use of AWT in GuiWebSocket? This would require you to move or eliminate the use of colorModel and virtualScreen from GuiWebSocket.

8. I understand that using wildcards in import statements may be controversial for users of IDEs. We are deliberately biasing our standards toward the case where someone doesn't have to use an IDE to read our code. At this time, it is out standard to always use wildcards for imports except in 2 cases:

  • A conflict in unqualified class names. We should place a comment there to explain it is individually imported because of a conflict.
  • When a class is from a package that we are trying to eliminate and/or which we want to minimize dependencies upon. One example of this is the java.awt.MouseEvent usage in the non-Swing UI code. Here we don't want to add other AWT references, so we also put an explicit comment to highlight that fact. Another example is the use of chui/ThinClient in non-chui code. We intend to eliminate those references and/or move the ThinClient to the ui/client/ package. Again, we try to leave behind a comment in these cases.

Where you must have AWT usage in non-Swing UI packages, please place a comment there to note that it is being used deliberately and that it should not be extended without some discussion. Those cases are OK to use individual imports. Other than those, please convert the imports to use the wildcard in the following classes: DrawImageOp (if it survives), @@,

9. There should always only be a single history entry for each trunk commit. Commits to the task branch are expected to be multiple and we don't want a history entry for each one. When multiple people are collaborating on changes to the same file in the same task branch, we have to combine our history entries into one single entry. Your changes add SBI history entries when we already have GES history entries. Please merge them in the following files: p2j.socket.js, GuiWebEmulatedWindow, GuiWebSocket, gui/driver/web/res/p2j.screen.js,

10. In history entries, don't put an extra ** blank line after your entry.

This:

** -#- -I- --Date-- --------------------------------Description----------------------------------
** 001 SBI 20150807 First version which describes a schema for Raw encoding and MD5 hash encoding
**                  that is used to transfer images md5 hashes via networks.
** 
*/

should be this:

** -#- -I- --Date-- --------------------------------Description----------------------------------
** 001 SBI 20150807 First version which describes a schema for Raw encoding and MD5 hash encoding
**                  that is used to transfer images md5 hashes via networks.
*/

Please fix this in ImageEncoding,

11. In the GuiWebEmulatedWindow.draw() processing for DRAW_IMAGE, you pass the entire PaintStructure to the web socket code. With all the other cases, we are hiding the internal knowledge of the PaintStructure from the web socket code. In other words, please pass all necessary member fields of the PaintStructure to the downstream code, instead of passing the PaintStructure instance itself.

12. Please always place a space on both sides of any binary operator. In p2j.socket.js function readInt32BinaryMessage(), the (3-i) should be (3 - i).

13. Javadoc for data members should not be split into multiple lines if it can fit into the 98 char limit. This:

   /**
    * Defines a pixels model as sRGB 32bits per a pixel
    */
   private ....

should be coded like this:

   /** Defines a pixel's model as sRGB 32bits per a pixel. */
   private ....

Please fix this in GuiWebSocket, gui/driver/web/res/p2j.screen.js,

14. Method javadoc should be follow these conventions:

  • Blank line between the text and the beginning of the parameters.
  • 4 spaces between the @param and the variable name.
  • The variable description for an @param should be on the next line, aligned with the variable name.
  • Blank line between the end of the parameters and the @return.
  • Honor the 98 char line limit except in sections.
  • Capitalized and punctuate sentences, including the descriptions used for @param, @return and @throws.
  • Don't add the data type into the @param description. The datatype is already listed in the parameter list itself and will be automatically shown in the javadoc. If you add such a description in the text, it is both duplicative and then we will have to maintain it if we change the type. Better to eliminate it.
  • Don't forget that the @param, @return or @throws should have descriptions.
  • Don't forget that the method itself should have some description in the main part of the javadoc.

This:

   /**
    * Encode the rectangular area on the screen to transfer.
    * @param screen virtual screen with the defined color model
    * @param x int a position of the left side of the target rectangle along the screen horizon
    * @param y int a position of the upper side of the target rectangle along the screen's vertical
    * @param w int a width of the target rectangular area
    * @param h int a height of the target rectangular area
    * @return
    */

should look like this:

   /**
    * Encode the rectangular area on the screen to transfer.
    *
    * @param    screen
    *           Virtual screen with the defined color model.
    * @param    x
    *           The position of the left side of the target rectangle along the screen horizon.
    * @param    y
    *           The position of the upper side of the target rectangle along the screen's
    *           vertical.
    * @param    w
    *           The width of the target rectangular area.
    * @param    h
    *           The height of the target rectangular area.
    *
    * @return   The encoded message contents.
    */

Please fix this in GuiWebSocket.drawImage(), GuiWebSocket.addDrawingOp(), ImageEncoding.getValue(), PixelsEncoder.packToBinaries(), DrawImageOp.execute() (if it survives), all methods of VirtualScreen (if it survives) and all methods of VirtualScreenImpl (if it survives).

15. Class level javadoc should not have extra * blank lines,

This:

/**
 * 
 * ImageEncoding defines Raw encoding and Hash encoding
 * that is used to transfer images ids instead of their binaries via networks.
 * 
 */

should be this:

/**
 * ImageEncoding defines Raw encoding and Hash encoding that is used to transfer
 * images ids instead of their binaries via networks.
 */

Please fix this in ImageEncoding, PixelsEncoder, RawEncoder and VirtualScreenImpl (if it survives).

16. All methods (including constructors) and data members must have javadoc. Please fix this in ImageEncoding.value, the ImageEncoding constructor, RawEncoder.TRUE_COLOR_BITS_PER_PIXEL, RawEncoder.packToBinaries() (it is OK to just copy the javadoc from the original method) and all methods of VirtualScreenImpl (if it survives).

17. Class definitions should be split into multiple lines:

This:

class RawEncoder implements PixelsEncoder

should be this:

class RawEncoder 
implements PixelsEncoder

Please fix this in RawEncoder and VirtualScreenImpl.

18. Curly braces for a class, method or block should always start on the following line.

This:

   public void drawImage(PaintStructure imageParameters) {
      addDrawingOp(drawImageOp.execute(imageParameters));
   }

should be:

   public void drawImage(PaintStructure imageParameters)
   {
      addDrawingOp(drawImageOp.execute(imageParameters));
   }

Also this:

      if (colorModel.getPixelSize() == TRUE_COLOR_BITS_PER_PIXEL) {
...
      } else {
         throw new UnsupportedOperationException("Supported only 32 bits per a pixel");
      }

should be this:

      if (colorModel.getPixelSize() == TRUE_COLOR_BITS_PER_PIXEL)
      {
...
      }
      else
      {
         throw new UnsupportedOperationException("Supported only 32 bits per a pixel");
      }

Please fix this in GuiWebSocket.drawImage(), GuiWebSocket.addDrawingOp(), RawEncoder.packToBinaries(), DrawImageOp.execute() (if it survives).

19. Don't put an extra blank line at the beginning or end of a class.

This:

class RawEncoder implements PixelsEncoder
{

   private final static int TRUE_COLOR_BITS_PER_PIXEL = 32;

...

      return encodedRectangle;
   }

}

should be this:

class RawEncoder implements PixelsEncoder
{
   private final static int TRUE_COLOR_BITS_PER_PIXEL = 32;

...

      return encodedRectangle;
   }
}

Please fix this in RawEncoder and VirtualScreenImpl (if it survives).

20. In control flow structures like for (;;), if (), while () or switch (), there should be a space between the keyword and the following open parenthesis.

This:

         for( ; i < numberOfPixels; i++, s++, p++ )
         {
            if( s == w )
            {

should be this:

         for ( ; i < numberOfPixels; i++, s++, p++ )
         {
            if ( s == w )
            {

Please fix this in RawEncoder.packToBinaries().

21. There should always be a blank line following each data member and following each method, except at the end of the class.

This:

   private final HashSet<Integer> loadedImages;
   /**
    * 
    * @param virtualScreen
    */
   public DrawImageOp(VirtualScreen virtualScreen)

should be this:

   private final HashSet<Integer> loadedImages;

   /**
    * 
    * @param virtualScreen
    */
   public DrawImageOp(VirtualScreen virtualScreen)

And this:

   /**
    * 
    * @return pixels int[] RGBA model via pixels 
    */
   int[] getPixels();
   /**
    * Gets a screen width.
    * @return int a screen width
    */
   int getScreenWidth();

should be this:

   /**
    * 
    * @return pixels int[] RGBA model via pixels 
    */
   int[] getPixels();

   /**
    * Gets a screen width.
    * @return int a screen width
    */
   int getScreenWidth();

Please fix this in DrawImageOp (if it survives), all methods of VirtualScreen (if it survives) and all data members of VirtualScreenImpl (if it survives).

22. Please have the entire method declaration fit on a single line if it doesn't exceed the 98 char line length limit.

This:

   public byte[] packToBinaries(VirtualScreen screen, int x, int y,
            int w, int h)

should be this:

   public byte[] packToBinaries(VirtualScreen screen, int x, int y, int w, int h)

Please fix this in RawEncoding.packToBinaries()

23. Please add blank lines between sections of code that are logically separate. Likewise, variable definitions can be column aligned. It enhances readability in my opinion. This one is highly subjective so you have to make your own call. But I think your code is too dense.

This:

   public byte[] execute(PaintStructure ps) {
      int x = ps.xOffset;//4
      int y = ps.yOffset;//4
      Image image = (Image) ps.img.getImage();
      int width = image.getWidth(null);//4
      int height = image.getHeight(null);//4
      virtualScreen.drawImage(image, x, y);
      int imageHash = image.hashCode();
      ImageEncoding encoding;
      if (!loadedImages.contains(imageHash)) {
         loadedImages.add(imageHash);
         encoding = ImageEncoding.RAW;
      } else {
         encoding = ImageEncoding.HASH;
      }
      final byte[] encodedImage = new RawEncoder().packToBinaries(virtualScreen, x, y, width, height);
      int msgLen = 22;
      if (encoding == ImageEncoding.RAW) { 
         msgLen += encodedImage.length;
      }
      byte[] drawImageMsg = new byte[msgLen];
      drawImageMsg[0] = (byte) (PaintPrimitives.DRAW_IMAGE.ordinal());
      writeMessageInt32(drawImageMsg, 1, x);
      writeMessageInt32(drawImageMsg, 5, y);
      writeMessageInt32(drawImageMsg, 9, width);
      writeMessageInt32(drawImageMsg, 13, height);
      drawImageMsg[17] = (byte) encoding.getValue();
      writeMessageInt32(drawImageMsg, 18, imageHash);
      if (encoding == ImageEncoding.RAW) {
         System.arraycopy(encodedImage, 0, drawImageMsg, 22, encodedImage.length);
      }

      return drawImageMsg;
   }

would probably be better like this:

   public byte[] execute(PaintStructure ps)
   {
      // parse the data
      int   x      = ps.xOffset; // 4 bytes
      int   y      = ps.yOffset; // 4 bytes
      Image image  = (Image) ps.img.getImage();
      int   width  = image.getWidth(null); // 4 bytes
      int   height = image.getHeight(null); // 4 bytes

      virtualScreen.drawImage(image, x, y);

      int imageHash = image.hashCode();
      ImageEncoding encoding;

      if (!loadedImages.contains(imageHash))
      {
         loadedImages.add(imageHash);
         encoding = ImageEncoding.RAW;
      }
      else
      {
         encoding = ImageEncoding.HASH;
      }

      final byte[] encodedImage = new RawEncoder().packToBinaries(virtualScreen, x, y, width, height);
      int msgLen = 22;

      if (encoding == ImageEncoding.RAW)
      { 
         msgLen += encodedImage.length;
      }

      byte[] drawImageMsg = new byte[msgLen];
      drawImageMsg[0] = (byte) (PaintPrimitives.DRAW_IMAGE.ordinal());
      writeMessageInt32(drawImageMsg, 1, x);
      writeMessageInt32(drawImageMsg, 5, y);
      writeMessageInt32(drawImageMsg, 9, width);
      writeMessageInt32(drawImageMsg, 13, height);
      drawImageMsg[17] = (byte) encoding.getValue();
      writeMessageInt32(drawImageMsg, 18, imageHash);

      if (encoding == ImageEncoding.RAW)
      {
         System.arraycopy(encodedImage, 0, drawImageMsg, 22, encodedImage.length);
      }

      return drawImageMsg;
   }

Please fix this in DrawImageOp.execute() (if it survives), RawEncoder.packToBinaries() and the VirtualScreenImpl constructor (if it survives).

24. Please don't use final for local variables unless it is required for access from inner classes. See the VirtualScreenImpl constructor (if it survives).

#811 Updated by Sergey Ivanovskiy over 8 years ago

1. Some of that logic duplicates code in >SwingEmulatedWindow. And you are missing much of the rendering logic in how SwingEmulatedWindow >handles the various features that can be >supported (e.g. cropping, convert3D, transparent, stretchToFit...). Ultimately we must handle all >these cases for the web client too. But >it seems like we can implement most of this in common code, right? This would involve moving most >of the image processing out of >SwingEmulatedWindow and into some other common location. There seem to be 2 options:
We can move this logic into the level of the AbstractGuiDriver. This is simpler, but it makes our image processing dependent upon AWT >for all future cases. Right now, we only have 2 kinds of GUI client. But someday we might want to implement a mobile GUI client (e.g. >Android or iOS). In such a case we might need to make this more abstract again.
We can create common helper classes that live outside of both gui/driver/swing/ and outside of gui/driver/web/. This is a bit more work >but is probably the better long term decision.

I will try the second approach to move common image processing to helper classes. The image processing code of SwingEmulatedWindow ( public void draw(final PaintStructure<Font, BufferedImage> ps)) uses x, y, width, height, image, xOffset and yOffset (which define offsets relative to the internal coordinate system of the target image), convert3D, transparent, stretchToFit and retainShape. These parameters need to draw the target image on the server side, but for a web client it is sufficient to send its position x and y, and its width and height.
And will follow 2, 3 and 4 to move functionalities of DrawImageOp to GuiWebDriver images drawing and their caching.

#812 Updated by Constantin Asofiei over 8 years ago

Greg, to reduce the resolution of font and text metrics, I'm adding caches at the AbstractGuiDriver class - this will reduce the overhead which will likely be seen on the web client.

Also, for the web driver, the font needs to be an integer index to a javascript-side font table. We can't work with AWT font here, so I'm changing the definition for GuiWebDriver and GuiWebEmulatedWindow to replace the F type with Integer instead of Font.

#813 Updated by Sergey Ivanovskiy over 8 years ago

Constantin, I am planning to move an image drawing from SwingEmulatedWindow to concrete implementation of new interface
public interface ClientImageDrawHelper<F, I> {
ImageWrapper<I> processImage(PaintStructure<F, I> ps);
}
public class BufferedImageDrawHelper<F> implements ClientImageDrawHelper<F, BufferedImage> {
...
}
And substituting for F depending on the driver type.

#814 Updated by Constantin Asofiei over 8 years ago

Sergey Ivanovskiy wrote:

Constantin, I am planning to move an image drawing from SwingEmulatedWindow to concrete implementation of new interface
public interface ClientImageDrawHelper<F, I> {
ImageWrapper<I> processImage(PaintStructure<F, I> ps);
}
public class BufferedImageDrawHelper<F> implements ClientImageDrawHelper<F, BufferedImage> {
...
}
And substituting for F depending on the driver type.

For images, the font is not used at all. To reduce complexity, try using

PaintStructure<?, I>
so the interface will be dependent only on I.

#815 Updated by Constantin Asofiei over 8 years ago

Greg, another question: how is the web socket supposed to receive data from the JS side, when, for example, the native width for a text is needed? Currently only user-key presses are sent to the java client-side. If this is not already supported, then, as the same socket is used for the user input too, we need to temporarily disable user input (and cache the keys?) while this API is being executed and re-enable it after all the data was read by the java-side.

#816 Updated by Greg Shah over 8 years ago

how is the web socket supposed to receive data from the JS side, when, for example, the native width for a text is needed? Currently only user-key presses are sent to the java client-side. If this is not already supported,

The current structure can be extended to include any kind of data that the JS side has to send:

1. Add one or more message types to WebClientMessageTypes. By convention, the codes with the high nibble set to 0 are the ones for transfer from JS to Java:

   /** Progress compatible keyboard message. */
   public static final byte MSG_KEY                 = (byte) 0x01;

   /** Virtual VT100 terminal keyboard message. */
   public static final byte MSG_KEY_VT100           = (byte) 0x02;

   /** Paste message. */
   public static final byte MSG_PASTE               = (byte) 0x03;

   /** Contents from the clipboard. */
   public static final byte MSG_CLIPBOARD_CONTENTS  = (byte) 0x04;

   /** Web client is requesting the current selection text. */
   public static final byte MSG_CLIPBOARD_PREPARE   = (byte) 0x05;

2. GuiWebSocket.processBinaryMessage() overrides the version in WebClientProtocol. This is where you should add new processing to handle the added message types. As you can see in those methods, for each message type we parse it in a custom manner according to the needs of the message.

3. The primary issue here is that processing through the web-socket is not synchronous. You will have to implement your own mechanism to wait on a response and to correlate a response with a given request. We do this with our own "queue" for keystrokes. For font metrics, you must do something similar.

4. Presumably, you will also need to add message types for Java to send to JS to request metrics or whatever. Again, you must add more message types to WebClientMessageTypes but this time by convention it is in the 0x8X section.

5. Map the font metrics calls in GuiWebDriver and GuiWebEmulatedWindow into web-socket method calls. Hide the message transfer details of each message type inside GuiWebSocket. Add read/write data helpers to WebClientProtocol or GuiWebSocket as makes sense so that useful constructs can be easily shared.

6. In p2j.socket.js there is an onmessage function, which is where the dispatching of incoming requests is done. Add sections for your new codes. These will generally call real functions that reside in p2j.screen.js or somewhere else to actually do the job of calculating the metrics. Since you shouldn't have to block on the JS side, you can send your response inline in the processing of these helpers.

then, as the same socket is used for the user input too, we need to temporarily disable user input (and cache the keys?) while this API is being executed and re-enable it after all the data was read by the java-side.

In practice, since the JS side is single threaded and all your work can be done inline with the font metrics request, you really shouldn't see much user input being sent over during the JS processing, except possibly something that was already in transit before the JS side started processing the font metrics request.

I'm not sure if we need to do much that is special. We already buffer up user input. If the processing on the java side blocks waiting for the font metrics responses, it may naturally work OK. Use of the common lock object is probably quite important to get that right.

#817 Updated by Constantin Asofiei over 8 years ago

Greg, I've added to branch 1811o rev 10943 java-side support for SET_FONT, SET_FONT_STYLE, DRAW_STRING, DRAW_STRING_SCALED, DRAW_PARAGRAPH plus font creation and font/text metrics operations. Next step is to implement the javascript-side code for font creation/set, font/text metrics, text drawing.

There might be some improvements related to transferring the byte data for a custom font (we might need to expose it via an URL), but I'll get to that after I get how the javascript side wants it delivered.

#818 Updated by Sergey Ivanovskiy over 8 years ago

3

and

6. I'd like to hear your ideas on how and when to remove images from the cache. This is both a Java and Javascript issue. We may want to >retain things longer than their "normal" lifetime in the 4GL code, in case they are used again within some reasonable time. But we also don't >want to keep things around forever.

Greg, could you correct me, because I don't understand clearly if it is required to implement or not. For an image we will store its usages. If a window uses an image than an image is referenced by this window and its reference list of image usages is increased and if a window is closed than we should traverse all images and remove this window from references of image usages. Should it be done?

#819 Updated by Greg Shah over 8 years ago

Greg, could you correct me, because I don't understand clearly if it is required to implement or not.

We do need to make sure that we clean up resources properly. This does need to be part of this DRAW_IMAGE work. But it can be the last thing that is done.

For an image we will store its usages. If a window uses an image than an image is referenced by this window and its reference list of image usages is increased and if a window is closed than we should traverse all images and remove this window from references of image usages.

Reference counting is a good strategy. Do we do that on the Java side? Then only when we remove an image on the Java side, would we notify the JS code to drop that image?

#820 Updated by Sergey Ivanovskiy over 8 years ago

Reference counting is a good strategy. Do we do that on the Java side? Then only when we remove an image on the Java side, would we notify >the JS code to drop that image?

Yes, for an example, we can add a parameter to destroy window message with a list of removed images. If you approve to split this part than I will update my local branch and commit into 1811o. Otherwise, I will do it when images removal implementation will be done.

#821 Updated by Greg Shah over 8 years ago

If you approve to split this part than I will update my local branch and commit into 1811o.

You can commit now.

#822 Updated by Sergey Ivanovskiy over 8 years ago

Greg, please review the changes. Committed revision 10944. Found conflicts:
Text conflict in src/com/goldencode/p2j/ui/client/driver/web/res/p2j.socket.js
Text conflict in src/com/goldencode/p2j/ui/client/gui/driver/web/GuiWebDriver.java
Text conflict in src/com/goldencode/p2j/ui/client/gui/driver/web/GuiWebEmulatedWindow.java
Text conflict in src/com/goldencode/p2j/ui/client/gui/driver/web/GuiWebSocket.java

#823 Updated by Greg Shah over 8 years ago

I am trying to finish work on resizing (as implemented by SwingEmulatedWindow.resizeScreenBuffers() and on repaint().

I see the following problems, for which the solution is not obvious.

1. In the Swing client SwingEmulatedWindow.resizeScreenBuffers() locks all other threads out and while the lock is owned, it resizes the internal buffer used to capture the drawn output. Part of that resizing draws the previous contents of the old buffer into the new resized buffer. We really have no counterpart to this in JS. Do we need this code at all in the GUI web client implementation? Is there a reason to block while we wait for the client JS side to do the resize?

2. After resizing and re-applying the current clipping/translate values, the SwingEmulatedWindow.resizeScreenBuffers() then releases all waiters and calls repaint(). The repaint() processing will force the JComponent parent of the ContentPane to paintComponent() and then it will call SwingEmulatedWindow.this.drawOnScreenBitmap();. It is really not clear to me what this is doing that is useful other than a flush(). In JS, the current flush() implementation just sends the accumulated drawing ops to the client. But in the Swing implementation, it actually blits the internal buffered image to the screen. I think that the use of the event thread for queuing the repaint processing may also have the effect of allowing the currently executing drawOnScreenBitmap() to finish and flush before the repaint occurs. Does this cause any newly expanded portion of the client to be drawn? I don't see how that would occur since it is not triggering a higher level rendering of the P2J widgets. It is really not clear what to do in the JS environment for this or why it is useful.

3. The web client doesn't have a natural event queue approach that would cause the repaint() to be executed on a deferred basis. I suspect that is not good. In particular, any call to repaint() from inside drawOnScreenBitmap() will result in a recursive call to drawOnScreenBitmap() which will not be deferred. That must break things.

4. Implementing repaint() using drawOnScreenBitmap() seems like it may not work even when called without the recursion.

On the current GUI web client implementation, we draw directly into the canvas (actually, the Canvas2DContext instance but it is basically the same thing). We don't keep an internal buffer for drawing. We can switch to such an approach. This will require us to implement a matching blit of the internal buffer during flush, which we don't have today. But it can be done. I just am not clear how important this is.

Even if I add this double buffering approach, it would need to be on the JS side. Any operations on it that must be synchronous will take extra effort in the GUI web client because we will have to wait for a notification from the JS side that the particular operation was completed. This can be done, but it is both awkward as well as a potential cause of deadlocks/performance bottlenecks.

Hynek/Constantin: Do either of you have some ideas on my questions above, especially in regard to the Swing client implementation? For reference, you can look at the task branch 1811o revision 10945 which has all my recent changes.

#824 Updated by Greg Shah over 8 years ago

please review the changes. Committed revision 10944.

This is significantly better. I like it quite a bit. Please do cleanup the coding standard issues, missing javadoc etc... in the new code. Otherwise it looks quite good.

#825 Updated by Hynek Cihlar over 8 years ago

Greg Shah wrote:

1. In the Swing client SwingEmulatedWindow.resizeScreenBuffers() locks all other threads out and while the lock is owned, it resizes the internal buffer used to capture the drawn output. Part of that resizing draws the previous contents of the old buffer into the new resized buffer. We really have no counterpart to this in JS. Do we need this code at all in the GUI web client implementation? Is there a reason to block while we wait for the client JS side to do the resize?

AFAIK resizeScreenBuffers() is only needed to manage the internal buffer in the double buffering approach. This should not be necessary on web client if double buffering is not used.

2. After resizing and re-applying the current clipping/translate values, the SwingEmulatedWindow.resizeScreenBuffers() then releases all waiters and calls repaint(). The repaint() processing will force the JComponent parent of the ContentPane to paintComponent() and then it will call SwingEmulatedWindow.this.drawOnScreenBitmap();. It is really not clear to me what this is doing that is useful other than a flush(). In JS, the current flush() implementation just sends the accumulated drawing ops to the client. But in the Swing implementation, it actually blits the internal buffered image to the screen. I think that the use of the event thread for queuing the repaint processing may also have the effect of allowing the currently executing drawOnScreenBitmap() to finish and flush before the repaint occurs. Does this cause any newly expanded portion of the client to be drawn?

Clearly not. In fact on the higher level, AbstractGuiDriver.resizeWindow() must be called just before AbstractContainer.repaint() (or similar) to cause the actual re-draw of the affected widgets. The need to call repaint() from SwingEmulatedWindow.resizeScreenBuffers() is thus questionable. Btw. there is another repaint() call in EmulatedWindowState.resizeWindow(), again this is probably not needed either.

3. The web client doesn't have a natural event queue approach that would cause the repaint() to be executed on a deferred basis. I suspect that is not good. In particular, any call to repaint() from inside drawOnScreenBitmap() will result in a recursive call to drawOnScreenBitmap() which will not be deferred. That must break things.

A solution to this would be to eliminate the repaint() call.

#826 Updated by Sergey Ivanovskiy over 8 years ago

Greg, these two units src/com/goldencode/p2j/ui/client/chui/driver/web/res/p2j.clipboard_helpers.js
and src/com/goldencode/p2j/ui/client/chui/driver/web/res/p2j.screen.js have misspelled jscript. Committed revision 10946.
But the clipboard selection in web chui client is broken now, if you are agree I will try to fix it.

#827 Updated by Greg Shah over 8 years ago

But the clipboard selection in web chui client is broken now, if you are agree I will try to fix it.

Please do.

#828 Updated by Sergey Ivanovskiy over 8 years ago

Greg, please review the fix. From https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style it follows that style property returns a typed object and can't be set to the custom properties holder object.Committed revision 10947.

#829 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

I am trying to finish work on resizing (as implemented by SwingEmulatedWindow.resizeScreenBuffers() and on repaint().
...
Hynek/Constantin: Do either of you have some ideas on my questions above, especially in regard to the Swing client implementation? For reference, you can look at the task branch 1811o revision 10945 which has all my recent changes.

Beside what Hynek noted, currently the web client's drawing is asynchronous. What I'm worried is that, if we give control back to the java side too early, and the JS is busy performing some complex drawing operations, the client-side might send something which was supposed to be drawn after the initial batch of primitives has finished (think of a menu-bar activation by the user, while a something else is still being drawn). We might get in trouble if JS side receives multiple draw requests, and it doesn't process them in the expected order.

#830 Updated by Greg Shah over 8 years ago

please review the fix

Overall, I'm fine with the fixes.

it follows that style property returns a typed object and can't be set to the custom properties holder object

I did a modified version of setOwnPropertiesTo() which only copies own properties and can handle any nested object, not just style. Also cleaned up the formatting and moved the function into a better location.

Does this version work for you?

#831 Updated by Greg Shah over 8 years ago

I did a modified version of setOwnPropertiesTo()

This is checked in as revision 10948.

#832 Updated by Greg Shah over 8 years ago

The following are the locations of calls to EmulatedWindowState.repaint():

  • GuiPrimitivesImpl.sync() as overridden in the SwingGuiDriver constructor. This will cause all pending ops to be drawn and flushed. This is probably OK.
  • GuiPrimitivesImpl.sync() as overridden in the GuiWebDriver constructor. This will cause all pending ops to be drawn and flushed. This is probably OK.
  • AbstractGuiDriver.setWindowLocation(). This will cause all pending ops to be drawn and flushed. This is probably OK. I wonder if this may be needed in cases where there are portions of the window that are now uncovered/visible which weren't before.
  • EmulatedWindowState.resizeWindow() which is called from AbstractGuiDriver.resizeWindow(). If this isn't called, then there might be other drawing operations added after the resize but before the resize is "realized". That may be why this was added and/or needed.
  • SwingEmulatedWindow.resizeScreenBuffers(), which as you note is probably something that is not needed. I'm eliminating this from the GUI web client so at least this one is not an issue.
  • EmulatedWindowState.endBatch() which is called from AbstractGuiDriver.endBatch() just after the END_BATCH op is added to the pending list. If not nested, then this seems OK. The design of the AbstractGuiDriver.endBatch() calls seems to account for nesting, but since it forces a drawing cycle each time it is called, can it really be nested? Perhaps I am just not properly thinking of how it is working.

Any further thoughts on the possibilities of recursion would be appreciated. Meanwhile, I have removed the resizeScreenBuffers() method from the GUI web client.

#833 Updated by Sergey Ivanovskiy over 8 years ago

I did a modified version of setOwnPropertiesTo()

Yes, it works properly.
Greg, please review my changes (committed rev 10950) to delete cached images on the client side.

#835 Updated by Greg Shah over 8 years ago

currently the web client's drawing is asynchronous. What I'm worried is that, if we give control back to the java side too early, and the JS is busy performing some complex drawing operations, the client-side might send something which was supposed to be drawn after the initial batch of primitives has finished (think of a menu-bar activation by the user, while a something else is still being drawn). We might get in trouble if JS side receives multiple draw requests, and it doesn't process them in the expected order.

We don't send anything to the JS side, until a flush() occurs. At that time, all the pending drawing ops are already "done" on the Java side and we transfer them as a single block to the JS side. We then return and allow the Java side to keep running.

On the JS side, it is possible that events could be forwarded to the Java side at the same time as drawing is being sent down. In fact, whatever is in the web socket queue at that point might very well be transferred at any time.

But since the JS side is single threaded, once the drawing ops message (which is a kind of compound message with many paint primitives embedded into a single binary message array) is picked up on the JS side, no other JS processing will happen until the drawing is complete. In other words, no event processing can occur during drawing.

We can only see events that have been fired on the JS side before the drawing ops message handling starts. By definition, I think that must be a prior interaction from the user that was done before any new drawing could be made visible.

I think this is probably OK, but we will have to see if there are cases where this somehow gets us into trouble.

#836 Updated by Hynek Cihlar over 8 years ago

Greg Shah wrote:

  • AbstractGuiDriver.setWindowLocation(). This will cause all pending ops to be drawn and flushed. This is probably OK. I wonder if this may be needed in cases where there are portions of the window that are now uncovered/visible which weren't before.

In this case I believe the OS should invalidate the window, and so the repaint() should not be needed. This is similar as when another window on top is moved. The OS may actually cache the window content and so the invalidation may be not needed at all.

  • EmulatedWindowState.resizeWindow() which is called from AbstractGuiDriver.resizeWindow(). If this isn't called, then there might be other drawing operations added after the resize but before the resize is "realized".

Why the pending draw operations would be an issue? I am probably missing something.

#837 Updated by Greg Shah over 8 years ago

Hynek Cihlar wrote:

Greg Shah wrote:

  • AbstractGuiDriver.setWindowLocation(). This will cause all pending ops to be drawn and flushed. This is probably OK. I wonder if this may be needed in cases where there are portions of the window that are now uncovered/visible which weren't before.

In this case I believe the OS should invalidate the window, and so the repaint() should not be needed. This is similar as when another window on top is moved. The OS may actually cache the window content and so the invalidation may be not needed at all.

The browser handles double buffering, blitting and z-order for us on the JS side. We must configure the canvas elements with absolute positioning and the z-index values that match the z-order values in our Java client.

There is no concept of invalidation processing in JS canvas. Anything you draw is immediately rendered into an off-screen buffer. The browser blits that buffer to the screen as needed, handling visibility/z-order for you in regard to other elements on the page. In our case we will have multiple canvas elements (one per 4GL window), but the canvas elements will be the only visible elements in the page. So the browser should be able to adequately manage the visibility of things for us.

On the negative side, we don't have any hooks into that layer to notify us about invalidity events, so should that become important we would have to try to detect these in some other way. I don't know of that issue so far, but we will see.

  • EmulatedWindowState.resizeWindow() which is called from AbstractGuiDriver.resizeWindow(). If this isn't called, then there might be other drawing operations added after the resize but before the resize is "realized".

Why the pending draw operations would be an issue? I am probably missing something.

It may not be. It just seems that the resize changes some core assumptions of how things draw, which means that all the following drawing operations will need to have been generated with the correct assumptions. To the degree that any drawing code is dynamically reading values for height/width of a window that have not yet been set at the time the drawing occurs, it would be an issue. Perhaps there are no such cases.

#838 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811o Revision 10950

The approach is good. My only concerns are related to coding standards.

1. Please don't import individual classes. See GuiWebDriver for java.util.Hash*. In that file already have a java.util.* import so the individual imports are redundant anyway. But if it wasn't there you should just add it using a wildcard. GuiWebEmulatedWindow has the same issue.

2. GuiWebDriver.addImageUsage() has its owning { on the wrong line. GuiWebDriver.removeUnusedImages() has this same problem.

3. I realize that cachedImages.length << 2 is a fast way to multiply integer data by 4. But it does not read as simply as cachedImages.length * 4. This causes the reader to consider the code more carefully to determine if you are using the variable as a bit field or if you are using a "trick" as a way of doing integer math. Since it is an uncommon idiom, I prefer you to write it in the common way, using the * operator. The slight gain in performance is not worth the reduction in readability. GuiWebSocket.destroyWindow() is an example of this usage.

4. In p2j.screen.js line 524, this:

               } else
               {

should be this:

               }
               else
               {

#839 Updated by Sergey Ivanovskiy over 8 years ago

Cleanup the code rev 10951.

#840 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811o Revision 10951

It looks good.

#841 Updated by Constantin Asofiei over 8 years ago

Greg, I have a second pass at the web font work (js-side). May I commit it?

#842 Updated by Greg Shah over 8 years ago

I just checked in revision 10952 which finishes the first pass at window resize. Some factors about resize can be seen from the attached example.

Constantin: yes, please do commit your font work 2nd pass.

#843 Updated by Greg Shah over 8 years ago

And the example...

#844 Updated by Greg Shah over 8 years ago

The following are some very useful articles on CSS z-order and positioning:

http://vanseodesign.com/css/css-positioning/
http://vanseodesign.com/css/css-stack-z-index/

This makes it clear why our use of absolute positioning and z-index is suitable for our one canvas is one 4GL window approach.

Absolute positioning does two things for us:

1. It takes the canvas elements out of the HTML layout processing. This allows us to implement the explicit positioning control that we need.

2. It is a positioning mode that honors z-order at the browser level. We can place all our 4GL windows at the exact z-order level we want using the z-index style property and the browser itself will implement the proper visual stacking.

The articles are worth a look since the z-order processing is really much more complex that it might seem. However, since we don't have any other (non-absolute) visual elements in our page, the z-index on each canvas will give us exactly the z-order control that we need.

#845 Updated by Constantin Asofiei over 8 years ago

Revision 10953 contains js-side support for font/text metrics and text drawing; a new p2j.fonts module was added, which contains a js-side font-table and all APIs required to compute font/text metrics. Also, some java-side fixes are included.

What is WIP are the paragraph height/drawing and custom font definition. I'm inclined in using the font-face tag to register the new font with a base64 encoded of the font bytes passed at the font's URL. An example how this can be used is here: http://www.sameratiani.com/2011/10/16/embed-inline-webfonts-in-css.html What I'm not sure at this time is cross-browser compatibility.

#846 Updated by Greg Shah over 8 years ago

I'm inclined in using the font-face tag to register the new font with a base64 encoded of the font bytes passed at the font's URL.

This seems reasonable.

An example how this can be used is here: http://www.sameratiani.com/2011/10/16/embed-inline-webfonts-in-css.html What I'm not sure at this time is cross-browser compatibility.

The core functionality is well supported:

http://caniuse.com/fontface

#847 Updated by Sergey Ivanovskiy over 8 years ago

Tested web chui client to MAJIC server running on devsrv01 using branch 1811o rev 10950, menus and clipboard selection works properly. gso-directory.xml should be changed to replace chuiWeb node by webClient in order to work with web client.

#848 Updated by Greg Shah over 8 years ago

Sergey: your next task is to investigate cursor management support for the GUI web client. This would be the backing functionality for GuiWebEmulatedWindow.setCursor(). Please review that method and the equivalent for SwingEmulatedWindow.

Please post your thoughts on an approach. And post any issues or concerns you have.

#849 Updated by Sergey Ivanovskiy over 8 years ago

From https://developer.mozilla.org/en-US/docs/Web/CSS/cursor the modern browsers support all interesting types of predefined cursors: auto, crosshair, default, move, text, wait, help, n-resize, e-resize, s-resize, w-resize,ne-resize, nw-resize, se-resize, sw-resize through its style object. It is only required to set canvas.style.cursor to the target value; for example, canvas.style.cursor = "wait" to change it to an hourglass or a watch cursor. Also they can display a custom cursor type via the cursors url.
We have two messages types related to a cursor, MSG_CURSOR_STATUS to hide it (chui mode) and MSG_CURSOR_POS to display it at the given position. We can reuse MSG_CURSOR_STATUS and extend it with cursor type or can add new MSG_CURSOR_TYPE to notify a web client about cursors changes.

#850 Updated by Constantin Asofiei over 8 years ago

Sergey Ivanovskiy wrote:

We have two messages types related to a cursor, MSG_CURSOR_STATUS to hide it (chui mode) and MSG_CURSOR_POS to display it at the given position. We can reuse MSG_CURSOR_STATUS and extend it with cursor type or can add new MSG_CURSOR_TYPE to notify a web client about cursors changes.

The MSG_CURSOR_POS and MSG_CURSOR_STATUS message types are not for the mouse cursor - these are for managing the caret (for text editing) on the ChUI screen. So please add some new message types to manage the mouse cursor in GUI.

#851 Updated by Greg Shah over 8 years ago

Rebased task branch 1811o from P2J trunk revision 10920. The latest revision is now 10961. This does include some history entry, javadoc and comment cleanup/additions.

This rebase was pretty painful. It is important to get this branch merged to trunk ASAP.

Please do a quick code review. If the two of you don't see anything of concern, then I will run regression testing on this update.

We also need to do some simple manual regression testing for GUI to make sure we haven't broken anything.

Sergey/Constantin: can the two of you split up this effort? The idea would be to execute a few basic testcases in 1811o and from the current trunk, so see if there is any difference. Constantin has the latest list of the programs that we should use for this, such as it is.

#852 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

Please do a quick code review. If the two of you don't see anything of concern, then I will run regression testing on this update.

Only issue I see is SwingEmulatedWindow.drawHelper is missing javadoc.

Also, the Swing GUI or ChUI code changes should be pretty safe.

#853 Updated by Greg Shah over 8 years ago

OK. I fixed the missing javadoc, added some comments and made some other code formatting cleanups in revision 10962.

I'm putting that revision into regression testing now.

#854 Updated by Sergey Ivanovskiy over 8 years ago

Constantin, Greg I found a strange behaviour (1811o) of a editor field from my clipboard_basic.p but haven't compared it with the current trunc version. The text input from keyboard creates a second input view above the current editor.

#855 Updated by Constantin Asofiei over 8 years ago

Greg, I can't convert this test, I get this:

./clipboard_basic.p
EXPRESSION EXECUTION ERROR:
---------------------------
throwException(errmsg)
^  { Unsupported method or attribute KW_VALUE. [COLON id <30064771369> 32:13] }
---------------------------
Elapsed job time:  00:00:02.674
ERROR:
java.lang.RuntimeException: ERROR!  Active Rule:
-----------------------
      RULE REPORT      
-----------------------
Rule Type :   DESCENT
Source AST:  [ : ] BLOCK/STATEMENT/KW_IF/KW_THEN/INNER_BLOCK/BLOCK/ASSIGNMENT/ASSIGN/COLON/ @32:13 {30064771369}
Copy AST  :  [ : ] BLOCK/STATEMENT/KW_IF/KW_THEN/INNER_BLOCK/BLOCK/ASSIGNMENT/ASSIGN/COLON/ @32:13 {30064771369}
Condition :  throwException(errmsg)
Loop      :  false

There is no support for KW_VALUE (VALUE attribute) in methods_attributes.rules.

#856 Updated by Constantin Asofiei over 8 years ago

Nevermind, I just noticed the testcases/uast/clipboard/clipboard_basic.p has different content...

#857 Updated by Sergey Ivanovskiy over 8 years ago

simple_sm.p

#858 Updated by Constantin Asofiei over 8 years ago

Greg, all found issues are already in trunk:

movie-ratings-dynamic.p
movie-ratings-static.p
- same as clipboard_basic.p, editor issues (related to layout I think)

calc.p
- layout issue

calc-static-chars.p
gui_btn_test4.p
- invalid frame ID

#859 Updated by Sergey Ivanovskiy over 8 years ago

These tests menu/popup_ext.p
window_sizing/create_empty_window.p
window_sizing/default_empty_window.p
window_sizing/test_runner.p are failed to start for 1811o. For all other tests if it gets another key instead of SPACE the test becomes frozen (may be deadlock)

#860 Updated by Constantin Asofiei over 8 years ago

Sergey Ivanovskiy wrote:

are failed to start for 1811o:

this is a conversion issue, three-d attribute doesn't convert properly (this is shared by window/session/frame/dialog-box)

For all other tests if it gets another key instead of SPACE the test becomes frozen (may be deadlock)

I can't duplicate this with 1811o - does this happen with trunk, too?

#861 Updated by Sergey Ivanovskiy over 8 years ago

Constantin, I need to retest because using old version of the testcase project, now my testcase project is up to date and the conversion is failed for my test suite on p2j trunc. For older version the conversion is done but I did not notice that some files were absent.

#862 Updated by Sergey Ivanovskiy over 8 years ago

Thus I excluded menu/simple_sm.p because the conversion for it is failed. Please see above the attached log.

#863 Updated by Constantin Asofiei over 8 years ago

Greg, this is a demo how to use font-face to dynamically define a font. base64 works, but I haven't considered something: this duplicates the font definition both in the browser's page and in memory, after is decoded. We can reduce the browser page footprint if the java client-side exposes the custom fonts via a URL, and use that URL to define the font.

#864 Updated by Greg Shah over 8 years ago

Thus I excluded menu/simple_sm.p because the conversion for it is failed.

We don't have any conversion changes in the 1811o branch. Any conversion problem should also occur in the trunk.

#865 Updated by Sergey Ivanovskiy over 8 years ago

Constantin

For all other tests if it gets another key instead of SPACE the test becomes frozen (may be deadlock)

I can't duplicate this with 1811o - does this happen with trunk, too?

For example this test ./rectangle/rect_test7_1.p has the same behaviour for trunc as for 1811o. If you press any sequence of keys except of SPACE, and then trying to press SPACE doesn't proceed and looks frozen. But it is reproduced sometimes.

#866 Updated by Sergey Ivanovskiy over 8 years ago

Greg

Thus I excluded menu/simple_sm.p because the conversion for it is failed.

We don't have any conversion changes in the 1811o branch. Any conversion problem should also occur in the trunk.

For me the tests
menu/simple_sm.p
window_sizing/create_empty_window.p
window_sizing/default_empty_window.p
window_sizing/test_runner.p are failed for both branches 1811o and trunc, all others behave the same. Please look at the previously attached png shots.

#867 Updated by Greg Shah over 8 years ago

The first main regression testing run has completed. The only failures are tc_dc_slot_025, tc_job_clock_002 and tc_pay_emp_abs_acc_003. I'm running a 2nds round right now.

#868 Updated by Greg Shah over 8 years ago

Constantin Asofiei wrote:

Greg, this is a demo how to use font-face to dynamically define a font. base64 works,

This is very good.

The downside of the URL approach is that it requires configuration, modification and management of the Jetty environment. There are security considerations as well.

Instead of using the URL approach, why not just send the font as a byte[] down to the JS client via the web socket? Then the same approach as in font_test4.html can be used. The variables used for the xfer will naturally be garbage collected.

but I haven't considered something: this duplicates the font definition both in the browser's page and in memory, after is decoded. We can reduce the browser page footprint if the java client-side exposes the custom fonts via a URL, and use that URL to define the font.

By "in the browser's page", do you mean the dynamically created @font-face elements in font_test4.html?

#869 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

but I haven't considered something: this duplicates the font definition both in the browser's page and in memory, after is decoded. We can reduce the browser page footprint if the java client-side exposes the custom fonts via a URL, and use that URL to define the font.

By "in the browser's page", do you mean the dynamically created @font-face elements in font_test4.html?

Yes - in font_test4.html the dynamically created @font-face rules will be added to the style node and this one will look like the one in font_test3.html, after it's added to the DOM; so the base64 encoded fonts will remain in the DOM - this is what I'm concerned.

#870 Updated by Sergey Ivanovskiy over 8 years ago

Greg, would you mind if I committed the cursor changes in 1811o?

#871 Updated by Greg Shah over 8 years ago

Please do NOT commit any changes to 1811o. If it passes regression testing, I am going to merge revision 10962 to trunk. As soon as I merge to trunk, I will create a 1811q branch and you will be able to check in there.

#872 Updated by Sergey Ivanovskiy over 8 years ago

Greg, please review my cursor local changes.

#873 Updated by Greg Shah over 8 years ago

Sergey: your next task is to implement line stroke support. To review our current implementation, please search on SET_LINE_STROKE, the ui/client/gui/driver/LineStroke.java and all the callers of setLineStroke(LineStroke stroke, float width).

Please also look at my other drawing work to get an idea of how I resolved the line anti-aliasing issue. Because of this anti-aliasing, I think that we may not be able to use the native JS like stroking features. Research the potential approaches. You should create a standalone sample that can exactly duplicate each of the currently used line strokes. Document your results and we will discuss any issues.

#874 Updated by Greg Shah over 8 years ago

Main regression testing has passed on the 2nd run (only GSO 237 failed, which passed on the 1st run, so this is considered passing).

CTRL-C testing is running now. If it passes, I will merge to trunk.

#875 Updated by Greg Shah over 8 years ago

MAJIC regression testing has passed for task branch 1811o. Manual Swing GUI testing and manual ChUI web testing did not identify any issues.

Merged to trunk as revision 10921. Task branch 1811o has been archived.

Created task branch 1811q from P2J trunk revision 10921.

Sergey: please apply your changes to 1811q.

#876 Updated by Sergey Ivanovskiy over 8 years ago

Committed to 1811q, cursor style changes, rev 10922.

#877 Updated by Greg Shah over 8 years ago

The attached example shows that only the resize operation requires us to redraw bits that were already drawn to the canvas. Moving a canvas is handled completely by the browser. Likewise, changes in z-order that cause portions of the canvas to be made visible, also require no special processing from the JS code. The browser handles it for us.

Operating the buttons works exactly as one would expect. Only the resize needs special attention. The current JS implementation already does copy the existing bits over to the newly resized canvas. Of course, any new space that is created will have to be drawn by the Java client in subsequent operations.

I will implement z-order management next.

#878 Updated by Constantin Asofiei over 8 years ago

Greg, I have some changes in p2j.fonts.js and p2j.socket.js related to custom font def - may I commit them?

#879 Updated by Greg Shah over 8 years ago

Constantin Asofiei wrote:

Greg, I have some changes in p2j.fonts.js and p2j.socket.js related to custom font def - may I commit them?

Yes, please do.

#880 Updated by Constantin Asofiei over 8 years ago

Revision 10923 contains the changes for custom fonts in the web client. It contains the @font-face approach with the base64-encoded font .

#881 Updated by Greg Shah over 8 years ago

Code Review Task Brnach 1811q Revision 10922

The cursor style changes look good. I only have these minor questions:

1. Why is import org.eclipse.jetty.websocket.api.annotations.WebSocket; needed in GuiWebSocket?

2. Doesn't the assignment of Window.prototype.setCursorStyle in p2j.screen.js need a terminating semicolon?

#882 Updated by Greg Shah over 8 years ago

Code Review Task Brnach 1811q Revision 10923

The custom font support looks good.

#883 Updated by Sergey Ivanovskiy over 8 years ago

1. Why is import org.eclipse.jetty.websocket.api.annotations.WebSocket; needed in GuiWebSocket?

GuiWebSocket extends annotated WebClientProtocol but i am not sure if @websocket annotation is inhereted?

2. Doesn't the assignment of Window.prototype.setCursorStyle in p2j.screen.js need a terminating semicolon?

Greg, fixed 1 and 2 in rev 10924. Semicolons can be omitted if two instructions are on the different lines. I did it unintentionally by mistakes but it follows from http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf (11.7Mb)
11.9.1
In the following rules, “token” means the actual recognized lexical token determined using the current lexical goal symbol as described in clause 11.
There are three basic rules of semicolon insertion:
  1. When, as a Script or Module is parsed from left to right, a token (called the
    offending token) is encountered that is not allowed by any production of the grammar, then a semicolon is automatically inserted before the offending token if one or more of the following conditions is true: The offending token is separated from the previous token by at least one LineTerminator.The offending token is }.The previous token is ) and the inserted semicolon would then be parsed as the terminating semicolon of a do-while statement (13.7.2).
  2. When, as the Script or Module is parsed from left to right, the end of the input stream of tokens is encountered and the parser is unable to parse the input token stream as a single complete ECMAScript Script or Module, then a semicolon is automatically inserted at the end of the input stream.
  3. When, as the Script or Module is parsed from left to right, a token is encountered that is allowed by some production of the grammar, but the production is a restricted productionand the token would be the first
    token for a terminal or nonterminal immediately following the annotation
    “[no LineTerminator here]”
    within the restricted production (and therefore such a token is called a restricted token), and the restricted token is separated from the previous token
    by at least one LineTerminator, then a semicolon is automatically inserted before the restricted token.

#884 Updated by Constantin Asofiei over 8 years ago

This is a demo for the paragraph drawing - it follows the same rules as the Swing implementation.

#885 Updated by Constantin Asofiei over 8 years ago

Greg, I've integrated the JS code from note 884 into p2j.screen.js and p2j.socket.js - let me know when I can commit it.

#886 Updated by Greg Shah over 8 years ago

You can commit it now.

#887 Updated by Constantin Asofiei over 8 years ago

The changes for paragraph layout/drawing are in rev 10925.

#888 Updated by Sergey Ivanovskiy over 8 years ago

Greg, please could clarify line stroke task. We can't use canvas native strokes because of lines and rectangles shapes are drawn by pixels in our implementation. It is due to anti-aliasing that causes shading lines. In the swing client there are 4 stroke styles including the default solid 1-pixel width style. Do you want to implement supports of this 4 style to draw rectangles shapes? We are using only rectangle shapes with these dots, small dots and small thin dots styles. Is it my task?

#889 Updated by Greg Shah over 8 years ago

Do you want to implement supports of this 4 style to draw rectangles shapes? We are using only rectangle shapes with these dots, small dots and small thin dots styles. Is it my task?

Yes. Consider that we may have to add stroke styles in the future. And it is possible that we will have to use stroke styles for shapes other than rectangles. Try to implement in a manner that can be extended, but for now you can focus on the current requirements as implemented in the Swing client.

#890 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811q Revision 10925

The changes look good.

1. I think the call to getFontHeight() in layoutParagraphWorker() can be moved outside of the loop since it should always return the same value.

2. In these "javadoc" comments in JS code, the {} construct is intended to encode the JS datatype.

So this:

    * @param    {y} y
    *           The drawing Y position, ignored if drawing is not required.

should be this:

    * @param    {Number} y
    *           The drawing Y position, ignored if drawing is not required.

#891 Updated by Sergey Ivanovskiy over 8 years ago

Yes. Consider that we may have to add stroke styles in the future. And it is possible that we will have to use stroke styles for shapes other than rectangles. Try to implement in a manner that can be extended, but for now you can focus on the current requirements as implemented in the Swing client.

Greg, I decided to implement this algorithm, in order to apply the given basic stroke to the shape we create a path of pixels coordinates {x:., y:.} following a clockwise direction along the outer edge of the given shape, and apply the given basic stroke like it would be a straight line of pixels. For a segment shape it gives a colour pattern according to the projection of this pixels segment along a vertical or a horizontal coordinate line depending if there is a unique inverse mapping of this segment shape pixels set to the corresponding projection along the selected coordinate line.

#892 Updated by Greg Shah over 8 years ago

I'm reviewing the current z-order implementation in the GUI client. Some thoughts:

1. GuiDriver.isOnTop() is not actually used. I'm removing it.

2. It seems like there are 2 operations that should move a window to the top of the z-order: getting focus and being made visible when the window is not currently visible. Are there any exceptions or am I missing something?

3. GuiDriver.stackWindows() would be very expensive to implement in its current form for the JS client. I'm trying to figure out if it is really needed for JS. It seems like it is there because of Swing/AWT implementation requirements. Can we make this an empty implementation for JS? JS has explicit support for z-index which as noted earlier in this task, is a direct mapping to z-order for our implementation. In addition, we have total control over this value (we can read/write it at any time). So if we still do need the stackWindows() implementation, I would just send the exact list of window IDs to the JS side and let it force the values directly. We shouldn't need the EmulatedWindowState.saveFocusListeners() and EmulatedWindowState.restoreFocusListeners(), since they are only used from stackWindows(). They are only there because the Swing/AWT implementation will fire focus events when the windows are moved to top. The JS implementation will not have that issue.

Constantin: I'm especially interested in your thoughts on whether stackWindows() is needed in the JS client.

#893 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

I'm reviewing the current z-order implementation in the GUI client. Some thoughts:

1. GuiDriver.isOnTop() is not actually used. I'm removing it.

You can remove EmulatedWindowState.isOnTop(), too.

2. It seems like there are 2 operations that should move a window to the top of the z-order: getting focus and being made visible when the window is not currently visible. Are there any exceptions or am I missing something?

Yes, these are the only two cases. But the JS driver should not check if the window is visible or not, when making the window visible and deciding to bring it to top - if is already visible, it must bring it to front regardless, as it's P2J window widget's responsibility to make it visible only if is hidden. My reasoning comes from the fact that the Swing's Window.setVisible does this (I think the win32 window follows the same rule) and there is P2J code which relies on this.

3. GuiDriver.stackWindows() would be very expensive to implement in its current form for the JS client. I'm trying to figure out if it is really needed for JS. It seems like it is there because of Swing/AWT implementation requirements. Can we make this an empty implementation for JS?

The reason for stackWindows() is to enforce the z-order at the OS level, as the Swing window is being moved to top each time it is made visible (and this is not how 4GL behaves when window families are involved in restore/de-iconification cases). So we can't have an empty implementation for JS.

So if we still do need the stackWindows() implementation, I would just send the exact list of window IDs to the JS side and let it force the values directly.

Yes, this is the way to go.

We shouldn't need the EmulatedWindowState.saveFocusListeners() and EmulatedWindowState.restoreFocusListeners(), since they are only used from stackWindows(). They are only there because the Swing/AWT implementation will fire focus events when the windows are moved to top. The JS implementation will not have that issue.

Correct.

On a side note, the JS side will need to fire focus gained/lost events when switching windows. But I don't think the JS side will require to fire implicit window maximized, iconified and deiconified events: all these will be available only via explicit actions from the user, from the window's buttons.

And on a final note: I think the swing client relies on an assumption that when a Swing window is gaining focus, it is moved automatically to top at the OS level. Look how the WindowManager.moveToTop is used: it is followed by a stackWindows() call only in WindowGuiImpl.hide. In the other case in TitledWindow.processEvents (when a window is activated), only the P2J internal state is updated. The JS side should behave the same: when a window is gaining focus, is moved automatically to top and a message is sent to the Java client to raise focus gained/lost events.

#894 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

Code Review Task Branch 1811q Revision 10925

The changes look good.

1. I think the call to getFontHeight() in layoutParagraphWorker() can be moved outside of the loop since it should always return the same value.

2. In these "javadoc" comments in JS code, the {} construct is intended to encode the JS datatype.

I've fixed these in rev 10926.

#895 Updated by Greg Shah over 8 years ago

Constantin: please implement mouse event support next. Consider the performance aspects. Minimize the number of round trips to the Java side. But at this time we won't do major refactoring of the Java logic to move significant portions to the JS side. I expect us to do that in a later phase.

#896 Updated by Greg Shah over 8 years ago

1. Why is import org.eclipse.jetty.websocket.api.annotations.WebSocket; needed in GuiWebSocket?

GuiWebSocket extends annotated WebClientProtocol but i am not sure if @websocket annotation is inhereted?

I don't think it is needed in the sub-class. In fact, we don't have anything to annotate, since the actual web-socket methods are not overridden in the sub-class.

Semicolons can be omitted if two instructions are on the different lines.

You make a good point. I think it is not a good practice to leave the semi-colons out. Thanks for fixing it.

#897 Updated by Sergey Ivanovskiy over 8 years ago

Greg, the performance issues have startled me, the usability test is not finished yet, but please look at the performance of drawRectangleSeries(); in the attached example based on the custom drawLine and drawRect. It takes almost 3 seconds to draw one rectangle.

#898 Updated by Sergey Ivanovskiy over 8 years ago

Greg, don't take it in account because it is due to pixel data is not cached in my test.

#899 Updated by Sergey Ivanovskiy over 8 years ago

Greg, please look at the line stroke test, the native implementation is able to represent small thin dots style, and don't know how to do this with pixels approach. The browser native api uses sub pixels to represent a half pixel.

#900 Updated by Greg Shah over 8 years ago

I have taken a look at the line stroke test. It definitely looks quite interesting, but it is hard for me to determine what exactly I'm looking at.

It is easier for me to understand an example that can be used to display the Java2D version side by side with the JS version of the same output. With your current approach, I can't see any Java2D output so it is hard for me to evaluate how well the JS example maps into our requirements. Please pattern the Java2D code after my GraphicsOpsExamples.java. Make sure to add specific examples of all forms of line stroking being currently used by P2J. Include enough variants to allow us to determine compliance when compared with the JS output. Then create a JS sample that renders the same thing. Please post the code and screen captures of the results so that it is easy to review.

We don't have to use the pixel-level drawing if the native JS results are the same as the Java2D results. If we cannot use the native JS line stroking, then we will discuss alternatives.

#901 Updated by Sergey Ivanovskiy over 8 years ago

Greg, 3 pictures, the first uses java 2D api, the second - native browser api and the third - custom jscript implementation that is almost done except of SPECIFIED_WIDTH stroke style.

#902 Updated by Greg Shah over 8 years ago

the first uses java 2D api, the second - native browser api and the third - custom jscript implementation that is almost done except of SPECIFIED_WIDTH stroke style

Good work. Please post the JS example used for this.

#903 Updated by Sergey Ivanovskiy over 8 years ago

Greg, please review, added strokes test and implemented SPECIFIED_WIDTH stroke style, it is a very first coming implementation (p2j.strokes.js)

#904 Updated by Greg Shah over 8 years ago

Line Stroking Review

This is really, really good! Go ahead and make your changes to 1811q to add stroking. Some thoughts:

1. I really like the approach to let our primitives (like drawLine()) return the path of pixels that was drawn. That approach means that our implementation is consistent and we reuse that code. If I understand correctly, we are then overdrawing those points to "wipe them away", if they shouldn't have been drawn in the first place. That approach works fine in the simple example, where there is nothing already there that may get overdrawn. But I think we will want to extend the drawing primitives to have a mode that doesn't draw when our stroking is non-default, but instead it just calculates the pixel path. Then the stroking portions can draw the resulting path. Did I misunderstand anything here? This one is probably important to fix first in the test code to make sure it is correct, before the merge to 1811q.

2. When you merge this into 1811q, the code needs thorough error handling to be added.

3. There are some minor code formatting issues and missing doc comments. Again, this is something to be fixed on the merge to 1811q.

#905 Updated by Constantin Asofiei over 8 years ago

Greg, about the mouse events. For the Swing implementation the call chain for a mouse event looks like this:
  1. MouseHandler is notified of an AWT mouse event
  2. MouseHandler's MouseAdapter implementation will use TC.postMouseEvent to post this event to the event queue.
  3. if there is an event processing loop active, the mouse event will be picked up and processed by TC.processProgressEvent. After the widget source associated for this mouse event is identified (in TitledWindow.processEvent), MouseHandler picks it up again and performs any MouseWidgetAction associated with that widget.

So we have a GuiDriver (capturing the OS mouse event) -> P2J (interpreting the mouse event) -> GuiDriver (for executing any draw actions for this mouse event) chain. This is problematic only when mouse movement comes into play: it will reach the P2J runtime for each mouse cursor move.

What I'm suggesting is for the driver to know which rectangles (in a window) can react to which mouse events. In case of enabled widgets, it will register with the driver (on a per-window basis) the boundary/area of this widget and which mouse actions is sensible to. This way, the JS driver will check first that there are (possible) widgets which might react to a certain mouse action and send the mouse event to the P2J client side only in those cases.

The first phase is the JS code which notifies the P2J side about any mouse events and any improvements will be made in a second phase.

#906 Updated by Constantin Asofiei over 8 years ago

A note for record keeping: the JS side needs implementation for the font aliases and underline font style.

#907 Updated by Greg Shah over 8 years ago

What I'm suggesting is for the driver to know which rectangles (in a window) can react to which mouse events. In case of enabled widgets, it will register with the driver (on a per-window basis) the boundary/area of this widget and which mouse actions is sensible to. This way, the JS driver will check first that there are (possible) widgets which might react to a certain mouse action and send the mouse event to the P2J client side only in those cases.

I like your idea on the rectangle approach. Combined with using the hit-testing capability in canvas, I think we can implement a pretty clean result on the JS side.

I've been considering the same idea for other event generation. I'm reviewing the usage of WindowListener (only used to get a notification about iconification/deiconification) and WindowStateListener (used to detect a window maximization event). I was thinking that we could setup hit-testing in rectangles that match the min (iconification) and max/restore buttons. That will allow us to directly map mouse events there to these window events.

I'm also thinking we will create our own taskbar equivalent, that will allow the de-iconification event and/or user-based window switching to occur.

#908 Updated by Sergey Ivanovskiy over 8 years ago

Line Stroking Review
1... That approach works fine in the simple example, where there is nothing already there that may get overdrawn. But I think we will want to extend the drawing primitives to have a mode that doesn't draw when our stroking is non-default, but instead it just calculates the pixel path. Then the stroking portions can draw the resulting path.

Greg, agree with you we need to add the drawing mode that just calculates the pixel path in order to draw outlines above the background. Planning to fix 1), 2), 3).

#909 Updated by Greg Shah over 8 years ago

By the way, the hit-testing implementation will have to be custom built by us. The CanvasRenderingContext2D.isPointInPath() does not lend itself easily to our usage. It can only work with the currently defined path. That means that only a single shape can be defined at a time. To use it to hit test in a list of rectangles, we would have to iterate through each rectangle, defining the path dynamically and then calling isPointInPath() and then clearing the path. This has to be done for each event, all this iteration seems too expensive. Instead, it seems easy to implement our own hit testing function.

#910 Updated by Sergey Ivanovskiy over 8 years ago

Fixed stroke drawing, the dash strokes performance becomes no more that 10 times slower than native performance for series lines and rectangles, for my configuration it is 97 ms for custom and 11 ms for native, respectively. Now have started to apply line strokes changes to 1811q.

#911 Updated by Greg Shah over 8 years ago

I've started trying to get the GUI web client running here. My thought is to test out the simplest hello world GUI program (just a message stmt).

Sergey: you were right that the GuiWebSocket does need to be annotated with the @WebSocket annotation. Otherwise it does not load.

I made that change and several others. I am checking those in now, to share them.

My next problem is this:

Aug 19, 2015 3:55:50 PM WebPageHandler 
INFO: {main} target=/ page=/com/goldencode/p2j/ui/client/driver/web/index.html
Aug 19, 2015 3:55:50 PM WebPageHandler 
INFO: {main} target=/index.html page=/com/goldencode/p2j/ui/client/driver/web/index.html
Aug 19, 2015 3:55:50 PM org.eclipse.jetty.server.Server doStart
INFO: jetty-9.1.2.v20140210
Aug 19, 2015 3:55:50 PM org.eclipse.jetty.server.handler.ContextHandler doStart
INFO: Started o.e.j.s.h.ContextHandler@67323b17{/,null,AVAILABLE}
Aug 19, 2015 3:55:50 PM org.eclipse.jetty.server.handler.ContextHandler doStart
INFO: Started o.e.j.s.h.ContextHandler@12c597ef{/,null,AVAILABLE}
Aug 19, 2015 3:55:50 PM org.eclipse.jetty.server.handler.ContextHandler doStart
INFO: Started o.e.j.s.h.ContextHandler@6a79d589{/client,jar:file:/home/ges/projects/1811q/build/lib/p2j.jar!/com/goldencode/p2j/ui/client/gui/driver/web/res,AVAILABLE}
Aug 19, 2015 3:55:50 PM org.eclipse.jetty.server.handler.ContextHandler doStart
INFO: Started o.e.j.s.h.ContextHandler@64b24bfb{/common,jar:file:/home/ges/projects/1811q/build/lib/p2j.jar!/com/goldencode/p2j/ui/client/driver/web/res,AVAILABLE}
Aug 19, 2015 3:55:50 PM org.eclipse.jetty.server.AbstractConnector doStart
INFO: Started ServerConnector@2157acdc{SSL-HTTP/1.1}{localhost:7449}
Aug 19, 2015 3:55:50 PM GenericWebServer.startup 
INFO: {main} Server URL: https://localhost:7449/
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at com.goldencode.p2j.ui.client.driver.web.WatchdogTimer.run(WatchdogTimer.java:80)
java.lang.NullPointerException
    at com.goldencode.p2j.ui.client.gui.driver.web.WebFontMetricsHelper.charWidth(WebFontMetricsHelper.java:67)
    at com.goldencode.p2j.ui.client.gui.driver.AbstractGuiDriver.scaleFont(AbstractGuiDriver.java:1141)
    at com.goldencode.p2j.ui.client.FontManager$WorkArea.createFont(FontManager.java:854)
    at com.goldencode.p2j.ui.client.FontManager$WorkArea.initialize(FontManager.java:816)
    at com.goldencode.p2j.ui.client.FontManager.init(FontManager.java:611)
    at com.goldencode.p2j.ui.chui.ThinClient.initializePost(ThinClient.java:2754)
    at com.goldencode.p2j.main.ClientCore.start(ClientCore.java:216)
    at com.goldencode.p2j.main.ClientCore.start(ClientCore.java:100)
    at com.goldencode.p2j.main.ClientDriver.start(ClientDriver.java:201)
    at com.goldencode.p2j.main.CommonDriver.process(CommonDriver.java:398)
    at com.goldencode.p2j.main.ClientDriver.process(ClientDriver.java:95)
    at com.goldencode.p2j.main.ClientDriver.main(ClientDriver.java:267)

When the client processes the MSG_GET_FONT_WIDTHS message, it calls getFontWidths() in ps.fonts.js. That code fails because fontTable[] is a 0 sized array.

Constantin: any ideas?

#912 Updated by Greg Shah over 8 years ago

Some points about my setup:

1. I converted a simple hello.p and I'm using ...Hello.execute as my entry point in the directory. I use this (the cvt_list has my programs to convert):

ant convert-all -D4gl.file.list=cvt_list
ant jar

This is all done in my ~/projects/p2j_stuff/testcases/uast/ directory, where I also have my 1811q p2j build symlink.

2. I'm using the standard directory.xml in simple/server/, with a minor change to point to a different spawn location.

3. I'm building like this:

ant all -Dpost.build=yes -Dspawn.install.folder=~/spawn/

4. I start the server like this:

cd ~/projects/p2j_stuff/testcases/simple/server/
P2J_HOME=/home/ges/projects/1811q/ ./server.sh -a ../../uast/build/lib/testcases.jar net:connection:secure=true net:server:secure_port=3334

5. I use https://localhost:7443/gui, open up the noscript support and have exceptions for the certificates.

Everything is working until we get into the JS client load, where it fails as noted above. BTW, the load is very slow...

#913 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

My next problem is this:

[...]

When the client processes the MSG_GET_FONT_WIDTHS message, it calls getFontWidths() in ps.fonts.js. That code fails because fontTable[] is a 0 sized array.

Constantin: any ideas?

Are there any messages in the console (CTRL-SHIFT-K for firefox)? If the fontTable[] is empty, it means it failed to create the font...

Also, about the slow loading: the default directory.xml from simple/server has the custom-fonts node defined: please rename it to something else (i.e. custom-fonts-disabled and try again - it should load faster, as it doesn't need to download and create the CSS style nodes with the base64 font definition.

#914 Updated by Greg Shah over 8 years ago

Are there any messages in the console (CTRL-SHIFT-K for firefox)?

Nothing useful. There are just some comments about using a SHA-1 certificate and a warning about char encoding in the index.html. None of these things are causing an issue.

#915 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

Are there any messages in the console (CTRL-SHIFT-K for firefox)?

Nothing useful. There are just some comments about using a SHA-1 certificate and a warning about char encoding in the index.html. None of these things are causing an issue.

Next step would be to debug into p2j.fonts.js createFont and see if it manages to finish properly and if the fontTable[] var gets updated.

#916 Updated by Greg Shah over 8 years ago

Next step would be to debug into p2j.fonts.js createFont and see if it manages to finish properly and if the fontTable[] var gets updated.

I tried that. It never gets called. In fact, the message 0x92 code in p2j.socket.js never executes either. Same for deriveFont() and the message 0x93 code. The getFontWidth() is called first.

#917 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

Next step would be to debug into p2j.fonts.js createFont and see if it manages to finish properly and if the fontTable[] var gets updated.

I tried that. It never gets called. In fact, the message 0x92 code in p2j.socket.js never executes either. Same for deriveFont() and the message 0x93 code. The getFontWidth() is called first.

That's strange, as GuiWebSocket.createFont is executed before anything else. Can you check without the custom-fonts node?

#918 Updated by Constantin Asofiei over 8 years ago

Greg, I've committed to rev 10929 some more fixes related to font creation/initialization. This time the web client advances and it gets to some window and translate related errors.

But it still bugs me that sometimes the initial createFont call never gets executed: is it possible that we start sending requests via the websocket BEFORE the Dojo had a chance to fully initialize? This might explain why I see intermittent results: while debugging the initial createFont request is processed, but if not debugging sometimes it doesn't get executed.

#919 Updated by Constantin Asofiei over 8 years ago

Sergey, I get these JS errors in the console:

ReferenceError: ctx is not defined p2j.screen.js:433:10
ReferenceError: pixData is not defined p2j.screen.js:964:6

#920 Updated by Sergey Ivanovskiy over 8 years ago

It is due to ctx must be changed to this.ctx and pixData to this.pixData. The draw method of Window class have many such errors, because ctx is defined as a field of Window class but it is accessed as a variable. I can fix these errors to the moment to prepare line stroke changes for a review, is it okay?

#921 Updated by Constantin Asofiei over 8 years ago

Sergey Ivanovskiy wrote:

It is due to ctx must be changed to this.ctx and pixData to this.pixData. The draw method of Window class have many such errors, because ctx is defined as a field of Window class but it is accessed as a variable. I can fix these errors to the moment to prepare line stroke changes for a review, is it okay?

Function drawPixel I think has more issues; even if I set this.pixData, it still doesn't work, because pixData is a member of the Window object - and drawPixel is a member of p2j.screen.

#922 Updated by Sergey Ivanovskiy over 8 years ago

Function drawPixel I think has more issues; even if I set this.pixData, it still doesn't work, because pixData is a member of the Window object - and drawPixel is a member of p2j.screen.

Constantin, usually IDE helps to fix it, but my eclipse from shared directory doesn't parse all js code tree, the private classes are hidden, also private methods are hidden from outline view. What is IDE you work with js? I think we should find js IDE with all these supports to work with pleasure.

#923 Updated by Constantin Asofiei over 8 years ago

Constantin Asofiei wrote:

But it still bugs me that sometimes the initial createFont call never gets executed: is it possible that we start sending requests via the websocket BEFORE the Dojo had a chance to fully initialize?

Placing a breakpoint in p2j.js init:99 function (before p2j.socket and others have a chance to initialize) proves this: P2J starts sending requests via the websocket before the JS side has initialized; the Java client is stuck waiting for a response, as it already sent the createFont request:

Object.wait(long) line: not available [native method]    
GuiWebSocket.waitForResult(byte) line: 1437    
GuiWebSocket.createFont(int, String, int, int, byte[]) line: 972    
GuiWebDriver.createFont(FontDetails<Integer>) line: 383    
FontManager$WorkArea.createFont(FontDetails<?>) line: 851    
FontManager$WorkArea.initialize(ServerExports) line: 816    
FontManager.init(ServerExports) line: 611    
ThinClient.initializePost(BootstrapConfig, Session, Object) line: 2754    

#924 Updated by Constantin Asofiei over 8 years ago

Sergey Ivanovskiy wrote:

Function drawPixel I think has more issues; even if I set this.pixData, it still doesn't work, because pixData is a member of the Window object - and drawPixel is a member of p2j.screen.

Constantin, usually IDE helps to fix it, but my eclipse from shared directory doesn't parse all js code tree, the private classes are hidden, also private methods are hidden from outline view. What is IDE you work with js? I think we should find js IDE with all these supports to work with pleasure.

I'm editing the javascript in a plain text editor currently. I'm using the browser to debug the JS code.

#925 Updated by Constantin Asofiei over 8 years ago

Greg, to fix the page-loading race issue I've added a new message type MSG_PAGE_LOADED = 0xFF:
  1. p2j.js will call p2j.socket.sendNotification(0xff); after all modules have initialized in me.init.
  2. a new EmbeddedWebServer.waitInitialization API was added, which is used by WebClientSpawner$TemporaryClientTask.doWork - after the web server was started, it calls waitInitialization which in turn waits until the MSG_PAGE_LOADED is received via websocket.

This way, the Java side can't start sending JS requests until the web page has initialized.

The code is common for both ChUI and GUI web clients.

I need to javadoc it and I can commit it.

#926 Updated by Greg Shah over 8 years ago

to fix the page-loading race issue I've added a new message type MSG_PAGE_LOADED = 0xFF

Nice work! This sounds like exactly the right solution.

I need to javadoc it and I can commit it.

Please do.

#927 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

to fix the page-loading race issue I've added a new message type MSG_PAGE_LOADED = 0xFF

Nice work! This sounds like exactly the right solution.

I need to javadoc it and I can commit it.

Please do.

In the end the correct place to send the message was the p2j.socket.onopen function - otherwise the message might still be sent using a not-yet-opened websocket by the JS side.

See revision 10931.

#928 Updated by Sergey Ivanovskiy over 8 years ago

Greg, please review the committed revision 10932.

#929 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811q Revision 10931

It looks good. The only thing to change is that ChuiWebDriver.waitInitialization() should be placed above the inner classes in the file. It would be good to put it right after the other delegated EmbeddedWebServer methods.

#930 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811q Revision 10932

This is really good. Note that I did check in some minor formatting fixes in rev 10933. Some thoughts:

1. GuiWebSocket.setLineStroke() should be implemented as a drawing operation. Thus it should use allocateAndSend() with PaintPrimitives.SET_LINE_STROKE and on the client side it should be handled in Window.prototype.draw(). There is no need for a new message type.

2. In p2j.screen.js the strokesManager needs javadoc. So too does LineStroke, dotStroke, dotSmallStroke, dotSmallThinStroke, defaultStroke, isDirectDrawingStrokeStyle() and getDefaultStrokeStyle() in p2j.strokes.js.

3. Should draw3DRect() be enabled for stroke support?

4. Should Window.prototype.save() and Window.prototype.replay() support saving/replaying the new line stroke state?

5. p2j.strokes.js is GUI-specific, but it is unconditionally loaded in index.html. Do we need an empty version in ui/client/chui/driver/web/res/?

#931 Updated by Sergey Ivanovskiy over 8 years ago

Greg, I fixed 1 and 2 in the committed revision 10934. Let me look at 3 and 4 due to we have no working example. To eliminate unconditional loading p2j.strokes.js with index.html we need to load this module from p2j.screen.js. It is possible to use https://dojotoolkit.org/documentation/tutorials/1.10/modules_advanced/index.html or another tricks to load p2j.strokes.js inside p2j.screen.js. Don't know if we leave ui/client/chui/driver/web/res/ without empty file leads to security issues?

#932 Updated by Greg Shah over 8 years ago

I've just checked in revision 10935 which has many, many fixes. Some of the problems were caused by unqualified references to functions that are now methods of Window. Some problems came from data and functions that were qualified with 'this' when they should not have been. The FILL_POLYGON code was modifying the 'idx' variable inside the drawing loop, which corrupted the message reading logic. Other fixes were applied too. The translate pop issues were not really translate issues, but rather were cases where there was a failure which aborted the drawing loop, leaving the state in a bad place.

There is now real drawing on the canvas! We are getting closer.

This version also has Java-side (to stderr) and JS-side (to console) logging to show the sequence of drawing operations.

The drawing is not 100% correct yet. Also there is some kind of problem interpreting the drawing message at some point (look in the console for 'Your session is corrupted buddy!').

Constantin: would you please look at this when you get in Friday? See if you can move this forward.

#933 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

The drawing is not 100% correct yet. Also there is some kind of problem interpreting the drawing message at some point (look in the console for 'Your session is corrupted buddy!').

Constantin: would you please look at this when you get in Friday? See if you can move this forward.

The "session corruption" was caused by re-using the i outer loop variable (for numOps) in a inner loop, by FILL_POLYGON. Also, I've fixed some other subtle message/offset errors. This can be found in 10936.

I'm looking into the drawing corruption next.

#934 Updated by Constantin Asofiei over 8 years ago

Sergey/Greg: I think the drawing corruption is related to the fact that when drawing pixels, this is not aware of the translated coordinates. When window.drawPixel is called (and any other similar APIs which work directly with pixels), it needs to use absolute coordinates, not relative coordinates. Also, canvas.putImageData must always receive absolute coordinates - this will not use the the canvas translated origin.

We have two choices:
  1. do this low-level (in drawPixel and others) and adjust the coordinates with the canvas translated coordinates. Question here: can we get the translated origin from the HTML canvas object? Or we need to maintain it on each window?
  2. do this high-level and ensure when APIs which end up calling drawPixel (and similar, maybe stroke-related code?) receive absolute coordinates (i.e. the coordinates are transformed to absolute coords using the canvas translated origin right after the coordinates are read from the message).

Let me know what you think.

#935 Updated by Sergey Ivanovskiy over 8 years ago

Constantin, you are right translate operations don't influence putImage results.
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/putImageData
ctx.fillStyle = "#000000";
ctx.fillRect(0,0,100,100);
var imagedata = ctx.getImageData(0,0,100,100);
ctx.putImageData(imagedata, 150, 0);

ctx.translate(50,50);
ctx.fillRect(0,0,100,100);
var imagedata = ctx.getImageData(0,0,100,100);
ctx.putImageData(imagedata, 150, 0);

#936 Updated by Sergey Ivanovskiy over 8 years ago

CanvasRenderingContext2D.currentTransform isn't supported by firefox, only chrome (https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/currentTransform) Thus inside functions that work with pixels we can't retrieve the current transform and do the required mapping to the physical screen coordinates.

#937 Updated by Sergey Ivanovskiy over 8 years ago

I found the way to determine translation coordinates
ctx.beginPath();
ctx.rect(0, 0, 1, 1);
var x;
var y;
for(var i = 0; i < canvas.width; i++) {
for(var j = 0; j < canvas.height; j++) {
if (ctx.isPointInPath(i, j)) {
x = i;
y = j;
break;
}
}
}
console.debug("x=" + x + ", y="+ y);

#938 Updated by Constantin Asofiei over 8 years ago

Explicitly maintaining the window translated origin provides better results.

#939 Updated by Constantin Asofiei over 8 years ago

And the modified p2j.screen.js used to get the drawing in note 938.

#940 Updated by Sergey Ivanovskiy over 8 years ago

Constantin, agree with you

Explicitly maintaining the window translated origin provides better results.

#941 Updated by Constantin Asofiei over 8 years ago

Constantin Asofiei wrote:

And the modified p2j.screen.js used to get the drawing in note 938.

To resume the changes:
  1. Window.prototype.drawLine - the coordinates are made absolute before drawing is started. The drawPixel is called using absolute coordinates. The path returned is in absolute coordinates.
  2. Window.prototype.drawImage - the putImageData is executed with absolute coordinates
  3. Window.prototype.drawRoundRect - the coordinates are made absolute before drawing is started
  4. Window.prototype.drawPolygon - the coordinates are made absolute for ctx.moveTo and ctx.lineTo

I'm pretty sure the changes in drawLine and drawImage are correct. What I'm note sure are the drawRoundRect and drawPolygon and the usage of the path returned by drawLine.

Sergey, can you double-check if the changes in the file I posted are OK? Also, please look through the JS code and see if there are other places I missed, which need absolute coordinates.

#942 Updated by Greg Shah over 8 years ago

Sergey: you get "extra points" for being creative in using the built-in canvas APIs to figure out the current translation. That was very clever.

But I think we all agree that the currently proposed approach is best. It is easier for us to maintain our own origin coordinates than to calculate it dynamically.

Constantin: please check in your p2j.screen.js changes.

Sergey: have you found any additional changes that are needed?

#943 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

Constantin: please check in your p2j.screen.js changes.

Committed to 1811q rev 10937.

#944 Updated by Greg Shah over 8 years ago

I think the fill portion of the drawPolygon() should not have any origin translation applied. The reason is that that code uses the native canvas API for creating the path and thus that code already honors the cumulative translations that the canvas is configured to use. Only the drawLine() usage uses our own pixel-based drawing and it is already fixed by your changes.

#945 Updated by Greg Shah over 8 years ago

drawRoundRect() has the same issue I think. It doesn't need the origin translation applied.

#946 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

I think the fill portion of the drawPolygon() should not have any origin translation applied. The reason is that that code uses the native canvas API for creating the path and thus that code already honors the cumulative translations that the canvas is configured to use.

You are correct, canvas lineTo and moveTo are aware of the canvas translations.

#947 Updated by Greg Shah over 8 years ago

With those changes (see rev 10938), we get this:

The polygon outline overdrawing seems incorrect, but it is closer overall.

By the way, the key processing works. Pressing space, cleanly exits the app.

#948 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

By the way, the key processing works. Pressing space, cleanly exits the app.

Have you managed to press ESC key to terminate the app? I've tried and it doesn't seem to be captured.

#949 Updated by Greg Shah over 8 years ago

Have you managed to press ESC key to terminate the app?

I think the current keyboard code is ChUI-specific. In ChUI, the ESC key acts like a modifier and is never passed to the 4GL directly. I have a task in our list to implement GUI compatibility for the key processing.

I never get the press ESC to exit the application when I run our Swing client or the web GUI. Is that something that the 4GL only does from the procedure editor?

Here is what my sample looks like when run in the P2J Swing GUI:

#950 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

I never get the press ESC to exit the application when I run our Swing client or the web GUI. Is that something that the 4GL only does from the procedure editor?

ESC in GUI acts like F4 in ChUI - it sends a STOP condition to the client. You can see this if you add a PAUSE or WAIT-FOR (followed by some logic) and press ESC - it will terminate the client.

#951 Updated by Greg Shah over 8 years ago

It looks to me like we have the following drawing problems:

1. The draw 3D rectangle is badly broken.
2. The fill polygon is not quite right.
3. The image drawing is not handling transparency properly.
4. The image drawing is not scaling properly.
5. For some reason the message statement output is being written 1 char at a time instead of an entire string on 1 line.

I will look into problems 1 and 2.

Sergey: please look at problems 3 and 4.

Constantin: Do you think the keyboard improvements should be done before the mouse support? I'll let you make the call.

#952 Updated by Sergey Ivanovskiy over 8 years ago

Greg, I am doing 3 and 4. From my previous task there are the following unresolved issues. Now we have additional properties strokeStyle and strokeWidth, is it true that lineWidth and strokeWidth should have the same values? Should the js canvas stroke be consistent with setLineStroke?

#953 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

Constantin: Do you think the keyboard improvements should be done before the mouse support? I'll let you make the call.

I want to finish the mouse support first and I'll take the keyboard improvements second. From what a I see, we need a JS implementation of the WinKeyboardReader APIs.

#954 Updated by Greg Shah over 8 years ago

Now we have additional properties strokeStyle and strokeWidth, is it true that lineWidth and strokeWidth should have the same values? Should the js canvas stroke be consistent with setLineStroke?

Hmmm. Good question. I'm not sure, but it possibly could affect things for drawRect(), draw3DRect() and drawPolygon().

It seems a good idea to make the canvas line stroking state consistent with our own custom drawing state. Please go ahead with it.

Greg, I am doing 3 and 4. From my previous task there are the following unresolved issues.

I understand. Please work on those when the drawImage() is working properly.

#955 Updated by Greg Shah over 8 years ago

The canvas line stroking state will definitely affect drawRoundRect(), because the corners are only rendered via the canvas APIs, we don't overdraw those today.

#956 Updated by Sergey Ivanovskiy over 8 years ago

Greg, please review the image resize fix committed revision 10939. There still exists another problem that was unseen before how to do the image background consistent with the current canvas state. We draw the gclogo.ico with transparent background on the server memory buffer and then send the buffer to the client. I think we need to leave pixels with a zero alpha channel on the client side?

#957 Updated by Greg Shah over 8 years ago

There still exists another problem that was unseen before how to do the image background consistent with the current canvas state. We draw the gslogo.ico with transparent background on the server memory buffer and then send the buffer to the client.

Do we need to:

1. Initialize the image drawing buffer using getImageData() to ensure that the original canvas contents are already there.
2. Implement our own "compositing" by detecting if a pixel in the server-sent image data is fully transparent and avoiding the copy for those pixels.

#958 Updated by Constantin Asofiei over 8 years ago

Greg, rev 10940 contains first pass at mouse support. Currently, the entire window area is registered for all mouse events: so everything is sent to the Java side; this will be refined later, to register individual mouse-aware areas.

I've tested a FILL-IN and the text is not drawn on the correct Y. Also, when pressing keys in a "fast mode", the client gets frozen... it might be another race condition, as the java side waits for the JS side to return a height for a text.

#959 Updated by Greg Shah over 8 years ago

I see something interesting in my debugging. I set a breakpoint in draw3DRect() and ran the client. The first time it halted at that point (which seems like it was not actually the first time it got called based on the console output), I see this on the screen:

I wonder if we are drawing some parts of the window more than once (and sometime in the wrong place and with different attributes). The titlebar area shows both the status text being overdrawn there AND the title text being drawn in a different size and/or font. The final result looks completely different but we may be overdrawing the "early" incorrect drawing.

#960 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811q Revision 10940

This is a really good update. I'll be interested to see how the widget registration/deregistration will be implemented. I was a bit surprised that you integrated the mechanism with the drawing ops. It seems to me that the reg/dereg will be done at a different time and with different frequency than the drawing, but I may not be considering everything you have thought of. Anyway, it looks good.

#961 Updated by Greg Shah over 8 years ago

I can't find any issues with the draw3DRect() or drawPolygon(). The implementation works as expected in all the cases I've checked.

There still remains a problem with the drawing of the titlebar background, the drawing of the min/max/close buttons, the drawing of the status area (as being an inset space with the raised corner triangle) and the drawing of the window border. I had thought that the problem was with draw3DRect() and drawPolygon(), but it seems the problem is elsewhere.

FYI, a useful way to "manually" test drawing code is to add something like this in p2j.screen.js:

   me.init = function(cfg) 
   {
      // example of how to add test code to the client (this is handy to test specific cases
      // without having it complicated by the full P2J stack):
      var temp = me.createWindow(100);
      temp.canvas.style.left = "700px";
      temp.canvas.style.top  = "15px";
      temp.resize(400, 400);
      temp.setColor([ 0, 255, 0 ]);
      temp.draw3DRect(temp.ctx, 5, 5, 100, 30, temp.rawColor, true, true);
      temp.draw3DRect(temp.ctx, 5, 40, 100, 30, temp.rawColor, true, false);
      temp.draw3DRect(temp.ctx, 5, 75, 100, 30, temp.rawColor, false, true);
      temp.draw3DRect(temp.ctx, 5, 110, 100, 30, temp.rawColor, false, false);

      var xPoints = [ 5, 35, 65 ];
      var yPoints = [ 145, 165, 145 ];
      temp.drawPolygon(temp.ctx, xPoints, yPoints, 3, [ 240, 240, 240 ], true);

      xPoints = [ 140, 110, 80 ];
      yPoints = [ 165, 145, 165 ];
      temp.drawPolygon(temp.ctx, xPoints, yPoints, 3, [ 240, 240, 240 ], true);
   };

Does anyone have other ideas of where to look for these issues?

#962 Updated by Greg Shah over 8 years ago

Hmm, looking at the console there are no errors. But the clipping coordinates are not always the same as the ones logged from the Java client stdout.

#963 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

I was a bit surprised that you integrated the mechanism with the drawing ops. It seems to me that the reg/dereg will be done at a different time and with different frequency than the drawing, but I may not be considering everything you have thought of. Anyway, it looks good.

I'm not fully decided yet to leave it at the drawing ops; initial thought is to have quick access at the window to which the widgets belong. I'm still thinking about the best way to capture the final widget dimension - I don't want to send intermediate sizes to the JS side.

#964 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

Hmm, looking at the console there are no errors. But the clipping coordinates are not always the same as the ones logged from the Java client stdout.

Are they completely bogus? Maybe there is some problem in reading the binary int32/int16.

#965 Updated by Sergey Ivanovskiy over 8 years ago

1. Initialize the image drawing buffer using getImageData() to ensure that the original canvas contents are already there.
2. Implement our own "compositing" by detecting if a pixel in the server-sent image data is fully transparent and avoiding the copy for those pixels.

Greg, please look at this formula for colours interpolation committed rev 10941. Around the gclogo icon there still exists some unresolved nimbus of white pixels
http://www.w3.org/TR/2dcontext/#pixel-manipulation
https://en.wikipedia.org/wiki/Alpha_compositing
Fixed committed revision 10944.

#966 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

I can't find any issues with the draw3DRect() or drawPolygon(). The implementation works as expected in all the cases I've checked.

The problem with the drawPolygon was that it needed an explicit ctx.closePath - otherwise a 3-point polygon was not being drawn for the status area corner.

Rev 10942 contains a fix for this and other issues related to font/string drawing and message processing.

#967 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

... and the drawing of the window border.

I don't think the window border is being drawn explicitly... the color is inherited from the AWT frame's background (as this is the default background for a frame in AWT).

#968 Updated by Constantin Asofiei over 8 years ago

Constantin Asofiei wrote:

Greg Shah wrote:

... and the drawing of the window border.

I don't think the window border is being drawn explicitly... the color is inherited from the AWT frame's background (as this is the default background for a frame in AWT).

The fix for the window border drawing is in 10943.

#969 Updated by Sergey Ivanovskiy over 8 years ago

Committed revision 10944. Fixed by sending non-premultiplied images pixels and calculating the middle value from an image pixel and its background pixel according to its alpha channel.

#970 Updated by Sergey Ivanovskiy over 8 years ago

... Maybe there is some problem in reading the binary int32/int16.

Constantin, our functions to read and write int32 pass this test, the key points are a byte order, a signed int and an unsigned int.

#971 Updated by Sergey Ivanovskiy over 8 years ago

Greg, please review the committed revision 10945. I have tried to resolve missed web browser console logs. May be it is related to the js UI thread is utilized extensively by onmessage websocket events. The first coming idea is to use web workers to perform all pixel operations, and then invokes js UI thread to putImageData on the canvas.

#972 Updated by Sergey Ivanovskiy over 8 years ago

Greg, what do you think if we will use http://log4javascript.org/docs/index.html logging js library in p2j.js? http://www.peopleware.be/blog/ developed another logging library based on it, http://www.peopleware.be/blog/entry/javascript-logging-in-dojo

#973 Updated by Greg Shah over 8 years ago

Attached is the current output of the GUI web client (left) and the same converted code in the Swing GUI client (right). Both are running against the same server at the same time.

Differences that I see:

1. Raised background of the titlebar is missing.
2. Fonts are significantly different.
3. Min/max/close button background is wrong.
4. The raised nature of the min/max/close buttons is missing (possibly this is one issue with 3 above).
5. The inset nature of the status area is not drawn right (possibly this is one issue with 3 above).
6. There is a dark outline around the close button (like a "focus" highlight), which doesn't exist in Swing.

Issues 3 - 5 present as a possible draw3DRect() issue, but my previous testing could not find an issue there. I haven't yet had the time to track down the clipping issue (note 962), but I will look at that once I have all code reviews done.

Constantin: any thoughts on item 2?

#974 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811q Revision 10941

I like the changes.

#975 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811q Revision 10942

The changes are good. Nice catch on the drawPolygon() fix.

Did your move of trans and clips into the Window object make the reported differences in clipping go away?

#976 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811q Revision 10943

It looks good.

#977 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811q Revision 10944

It looks good.

#978 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811q Revision 10945

Good changes.

#979 Updated by Greg Shah over 8 years ago

I have tried to resolve missed web browser console logs. May be it is related to the js UI thread is utilized extensively by onmessage websocket events. The first coming idea is to use web workers to perform all pixel operations, and then invokes js UI thread to putImageData on the canvas.

I think this is a good idea. Have you checked how well it works or if your idea is correct?

If so, it may make sense to handle all the websocket communication on the web worker thread.

#980 Updated by Greg Shah over 8 years ago

what do you think if we will use http://log4javascript.org/docs/index.html logging js library in p2j.js? http://www.peopleware.be/blog/ developed another logging library based on it, http://www.peopleware.be/blog/entry/javascript-logging-in-dojo

I'm not sure. I prefer to avoid additional library requirements. Soon, we may get rid of our dojo usage. It is really only needed for the ChUI web client and even there it is only used for the popup menu support. At a minimum, we will eliminate its loading in GUI. We can implement our own menuing and get rid of a pretty costly extra library.

Unless the log4javascript really solves some important issue, I prefer to avoid it.

#981 Updated by Sergey Ivanovskiy over 8 years ago

Greg, I have some arguments to use log4javascript-1.4.13 or to implement own logger. The first one browser console api is not managed by our application, but log4javascript-1.4.13 or custom is fully controlled, the second the browser console loses messages at the start of application, so we can't check the server log with the client log. We can write our custom logger but really log4javascript-1.4.13 is more functional and is similar to java log4j api, this logger can be removed on the production by setting to the stub object provided by this library.
Dojo uses AMD format ( http://requirejs.org/docs/whyamd.html ) for modules and it is not loaded all to client, only some required modules by the client. log4javascript-1.4.13 supports AMD format.

#982 Updated by Sergey Ivanovskiy over 8 years ago

It can help to evaluate log4javascript.js The diff file misses log4javascript.js, it must be copied to the folder with p2j.js.

#983 Updated by Greg Shah over 8 years ago

the browser console loses messages at the start of application, so we can't check the server log with the client log

Actually, output is lost in many places of the log.

But I'm not sure it is an issue related to the console itself. If we want to test something simple, we can just implement our own array and use push() to add a string to the end. This will be faster than the console because there is no need to log the source code and line number, which is probably the slowest part.

The thing is that I wonder if the problem is really related to the onmessage() getting interrupted by another incoming onmessage(). I saw several cases during debugging that suggested this might be happening.

My recent comparisons do show that there is still some differences in logged output, though most are the same.

#984 Updated by Greg Shah over 8 years ago

unexpected problems to direct dojo loading custom AMD js modules

Our Dojo is not loading the normal way you might expect. We have custom code in the embedded Jetty server to load Dojo code from the zip included in our classpath.

Since we really don't need anything from Dojo in the GUI web client, I really don't want to include it in our client. I understand that it can be made smaller. But removing it completely is more secure and higher performance.

For now, please don't spend any time on loading extra modules. I think your webworker idea has more promise as a real solution to our performance issue.

#985 Updated by Greg Shah over 8 years ago

I see this when the client starts (output to Java stderr):

java.lang.NullPointerException
    at com.goldencode.p2j.ui.client.gui.driver.web.WebFontMetricsHelper.charWidth(WebFontMetricsHelper.java:67)
    at com.goldencode.p2j.ui.client.gui.driver.AbstractGuiDriver.scaleFont(AbstractGuiDriver.java:1151)
    at com.goldencode.p2j.ui.client.FontManager$WorkArea.createFont(FontManager.java:854)
    at com.goldencode.p2j.ui.client.FontManager$WorkArea.initialize(FontManager.java:816)
    at com.goldencode.p2j.ui.client.FontManager.init(FontManager.java:611)
    at com.goldencode.p2j.ui.chui.ThinClient.initializePost(ThinClient.java:2754)
    at com.goldencode.p2j.main.ClientCore.start(ClientCore.java:216)
    at com.goldencode.p2j.main.ClientCore.start(ClientCore.java:100)
    at com.goldencode.p2j.main.ClientDriver.start(ClientDriver.java:201)
    at com.goldencode.p2j.main.CommonDriver.process(CommonDriver.java:398)
    at com.goldencode.p2j.main.ClientDriver.process(ClientDriver.java:95)
    at com.goldencode.p2j.main.ClientDriver.main(ClientDriver.java:267)

#986 Updated by Greg Shah over 8 years ago

I'm not sure this is a real problem. I only saw it 4 times and that was while I also had an invalid character in p2j.screen.js such that it would not load. The failure may have been some related problem caused by my other issue.

#987 Updated by Greg Shah over 8 years ago

I just committed 10946. It has a temporary solution to the console log issue. It is not suitable for production use, but it should work for now.

Using this, I captured the log text and did a comparison. There is nothing missing and after removing some differences (e.g. FLUSH and START_BATCH only appear in the Java output), there were no real differences in the results.

The only minor thing that is different is that the server-side FLUSH generates a START_DRAWING_CYCLE/END_DRAWING_CYCLE on the JS side that never appears on the Java side. I'm not sure this is a real issue, but it is the only substantive difference. The clipping and everything else matches.

#988 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

Constantin: any thoughts on item 2?

2. Fonts are significantly different.

I'm looking into this.

The WindowTitleBar code draws the title bar like this:
  1. draws a 3D-rectangle (using fill3DRect) and fills it with color C1 (if active) or C2 (if inactive), before drawing the icon, title and caption buttons
  2. when drawing the actual title, it fills a rectangle with the same color (C1 or C2), using fillRect, before drawing the title

I think the difference is in the color usage for fill3DRect - it uses draker/lighter colors, when it should not change the color: it should only draw the border raised or lowered, the correct color is set by the Java side.

#989 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

Code Review Task Branch 1811q Revision 10942

The changes are good. Nice catch on the drawPolygon() fix.

Did your move of trans and clips into the Window object make the reported differences in clipping go away?

No, this was because the intermediate flush you noticed in note 987 is starting a separate drawing cycle, so the state of these two vars were not consistent.

#990 Updated by Greg Shah over 8 years ago

I think the difference is in the color usage for fill3DRect - it uses draker/lighter colors, when it should not change the color: it should only draw the border raised or lowered, the correct color is set by the Java side.

You are definitely on to something here. It is a bit more complicated in the end because the changes only relate to raised mode. In raised mode the fill needs to be the original color instead of the lighter color. In addition, in raised mode we must overdraw the top and left borders with the lighter color. In our example above with green, the lighter color is the same as the original color so it masked both of these issues. With the active titlebar blue (which is 153, 180, 209 or 0x99B4D1), the difference can be properly seen.

Here is the fixed standalone JS example code in output comparison with Java in blue:

I've tested the standalone code with the original green color too and that is also fine with the changes.

I then tested the changes in p2j.screen.js. They DIDN'T fix the issue!

I found one more problem in our code. The way we were reading the raised flag was backwards (we were comparing the byte to 0 and setting raised to true if it was 0, when we should have been comparing to 1). Since all the other drawing fixes only affected raised mode, this made a BIG difference! Here is the result:

These changes are checked in as rev 10947.

Differences that I see:

1. The raised borders of the titlebar are missing. It looks like they may have been drawn 1 pixel further out than in Java and thus they are overdrawn by the window border and the window client area.
2. The inset borders of the status area seem to be drawn 1 pixel outside of where Java draws, this is probably the same issue as 1.
3. Fonts are significantly different.
4. There is a dark outline around the min/max/close buttons. The min/max buttons have dark grey and the close button has black (like a "focus" highlight). None of this drawing exists in Swing GUI.

I'm going to work on issues 1 and 2 next.

#991 Updated by Sergey Ivanovskiy over 8 years ago

For now, please don't spend any time on loading extra modules. I think your webworker idea has more promise as a real solution to our performance issue.

Greg, please direct me what should be done at first? I would like to do pixels operations with help of Worker. For example, p2j.screen.js uses postMessage to send drawing commands to p2j.repaint_manager.js (Worker). It (p2j.repaint_manager.js) gets drawing commands to draw pixels in the memory screen buffer, then sends the target buffer back to p2j.screen.js to draw on the screen canvas. Can I proceed with this? It needs more time than it is supposed. Also to move web socket communications into Worker(for example, p2j.socket_peer.js) we need a communication protocol between to p2j.socket.js and p2j.socket_peer.js because the window global object is not accessed inside Worker, p2j.socket_peer.js. What would you like from me to do the first for evaluation?

#992 Updated by Sergey Ivanovskiy over 8 years ago

To be more concrete let us consider this example
Window.prototype.drawRect = function(ctx, x, y, width, height, color, fill) {
// use vector operations for the interior of the rectangle
if (fill) {
ctx.beginPath();
ctx.rect(x, y, width, height);
renderClosedPath(ctx, fill);
}
The pixels operation gets the canvas image data and then sends it to the repaint manager (Worker) with command drawLine
The following code moves to the repaint manager (Worker)

// now overdraw the stroked portion to eliminate anit-aliasing
// to draw rectangle following a clockwise direction
var path = this.drawLine(ctx, x, y, x + width, y, color);
Array.prototype.push.apply(path, this.drawLine(ctx, x + width, y + 1, x + width, y + height, color));
Array.prototype.push.apply(path, this.drawLine(ctx, x + width - 1, y + height, x + 1, y + height, color));
Array.prototype.push.apply(path, this.drawLine(ctx, x, y + height, x, y + 1, color));// close the path

return path;
};

Using this way drawing commands should be separated to pixels and vectors operations and since the order is important, their all should be send to repaint manager but only pixels will be drawn by it.

#993 Updated by Sergey Ivanovskiy over 8 years ago

Greg, at first I can take this task 4. There is a dark outline around the min/max/close buttons. The min/max buttons have dark grey and the close button has black (like a "focus" highlight). None of this drawing exists in Swing GUI. ?

#994 Updated by Greg Shah over 8 years ago

Sergey Ivanovskiy wrote:

Greg, at first I can take this task 4. There is a dark outline around the min/max/close buttons. The min/max buttons have dark grey and the close button has black (like a "focus" highlight). None of this drawing exists in Swing GUI. ?

Good idea. Yes, please do.

#995 Updated by Greg Shah over 8 years ago

The reduction in console usage was enough to solve the current performance issue. I think it is best to get the functionality of the client finished and then look at the places needing performance improvements. The web worker idea will certainly be something we would consider. But for now, let's pause that work.

In regard to the line stroking open issues:

3. Should @draw3DRect()@ be enabled for stroke support?

4. Should @Window.prototype.save()@ and @Window.prototype.replay()@ support saving/replaying the new line stroke state?

5. @p2j.strokes.js@ is GUI-specific, but it is unconditionally loaded in @index.html@. Do we need an empty version in @ui/client/chui/driver/web/res/@?

I think we are done with both item 3 and item 4, right?

For number 5, instead of worrying about conditional loading of modules (e.g. AMD), let's just implement an empty version in the chui driver, so that it is safe to use. Please go ahead with adding that.

#996 Updated by Greg Shah over 8 years ago

Constantin: do you have a more functional version of mouse support that can be checked in (even if it isn't the optimized version)?

#997 Updated by Greg Shah over 8 years ago

Attached are logs taken with rev 10949. The JS and Java sides of the web client output are basically the same. The Swing GUI log however, has some non-trivial drawing at the end that is not present in the web GUI. I wonder how much of our drawing issues are related to these missing operations.

#998 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

Constantin: do you have a more functional version of mouse support that can be checked in (even if it isn't the optimized version)?

I need to finish cleaning it up and I think I'll commit it today.

#999 Updated by Greg Shah over 8 years ago

Revisions 10950 and 10951 fix draw3DRect() and drawRect() (fill mode only) respectively. With these changes some of the raised/inset highlights are now visible for the titlebar and status area, but there is still something wrong such that they are not 100% correct. In addition, these changes fix the borders to exactly match the thickness of the ones drawn in the Swing GUI client (they were off in 2 dimensions before).

#1000 Updated by Sergey Ivanovskiy over 8 years ago

Greg, please look at these changes, I have tested how to draw the window close button exactly like it is done by our application using

// the window close button
var temp = me.createWindow(100);
temp.canvas.style.left = "700px";
temp.canvas.style.top = "15px";
temp.resize(400, 400);
temp.setLineStroke(0, 1);
temp.setColor([ 240, 240, 240 ]);
//fill3DRect
temp.draw3DRect(temp.ctx, 0, 0, 19, 17, temp.rawColor, true, true);
temp.setColor([ 0, 0, 0 ]);
temp.setLineStroke(1, 2);
var path = temp.drawLine(temp.ctx, 4, 3, 15, 14, temp.rawColor);
strokesManager.applyStrokeToPath(temp.ctx,
temp.strokeStyleId,
temp.strokeWidth,
temp.rawColor,
path);
path = temp.drawLine(temp.ctx, 4, 14, 15, 3, temp.rawColor);
strokesManager.applyStrokeToPath(temp.ctx,
temp.strokeStyleId,
temp.strokeWidth,
temp.rawColor,
path);
path = temp.drawLine(temp.ctx, 5, 14, 16, 3, temp.rawColor);
strokesManager.applyStrokeToPath(temp.ctx,
temp.strokeStyleId,
temp.strokeWidth,
temp.rawColor,
path);
temp.setColor([ 240, 240, 240 ]);
path = temp.drawRect(temp.ctx, 2, 2, 15, 2, temp.rawColor, true);
strokesManager.applyStrokeToPath(temp.ctx,
temp.strokeStyleId,
temp.strokeWidth,
temp.rawColor,
path);
path = temp.drawRect(temp.ctx, 2, 14, 15, 2, temp.rawColor, true);
strokesManager.applyStrokeToPath(temp.ctx,
temp.strokeStyleId,
temp.strokeWidth,
temp.rawColor,
path);
temp.setColor([ 0, 0, 0 ]);
temp.setLineStroke(0, 1);
All artifacts are followed from these sequence, the 2 pixel with and the light horizontals segments crossed the x symbol.

#1001 Updated by Sergey Ivanovskiy over 8 years ago

Greg, I think draw3DRect and drawRect are not correct. I will try to fix it.

#1002 Updated by Constantin Asofiei over 8 years ago

Branch 1811q rev 10952 contains second phase of mouse support. Remaining mouse issues:
  1. window resize: the cursor is not restored in Swing
  2. test/fix window resize in Web
  3. specify mouse actions for remaining widgets (and test). Only FILL-IN and BUTTON are aware of mouse actions in rev 10952.
  4. drawing (as response to text input or mouse click) is still laggish. I'll wait to see if the webworkers will improve this.
WIP font issues:
  1. I've fixed some issues related to the JS font-table.
  2. I think for the Tahoma bold a separate true-type font definition is needed (At least this is how it is defined in Windows)
  3. even so, the window title's height is larger: are we sure we need a hard-coded 96 DPI for the web driver? Because setting it to 72 produces the same drawn string, in Windows/Firefox. So I wonder if 96DPI is not something CSS specific, and Canvas text drawing is more related to the OS DPI, not CSS.

#1003 Updated by Greg Shah over 8 years ago

Revision 10953 has major fixes for draw3DRect(). With this (and prior fixes), the result is now:

I believe the titlebar is now fully correct. The status inset is close but the right side sticks out in Java2D when it doesn't in JS. I'll review the remaining differences in more detail tomorrow.

Greg, I think draw3DRect and drawRect are not correct. I will try to fix it.

Sergey: I have put many fixes into the 1811q branch for both of these. Please make sure you are using the latest version in your testing, before you make any changes. Be very careful in comparing the output of the standalone case that is attached. I know they are pretty close to exactly correct now, so if there are any changes needed it is not much.

#1004 Updated by Sergey Ivanovskiy over 8 years ago

Okay, I am using the current version, only please note that draw3DRect doesn't draw 3-D outline and if we use this.ctx.stroke(); after drawLine, it displays strokes of the current path. With this diff p2j.screen_diff_2.txt window buttons look like on the webClient.png

#1005 Updated by Sergey Ivanovskiy over 8 years ago

Greg, please review the committed revision 10954. It is a fix for window caption buttons, and p2j.strokes.js, empty stub module for chui, is added.

#1006 Updated by Hynek Cihlar over 8 years ago

Constantin Asofiei wrote:

Branch 1811q rev 10952 contains second phase of mouse support. Remaining mouse issues:
  1. window resize: the cursor is not restored in Swing

Fixed as part of task branch 2424c.

  1. test/fix window resize in Web

What kind of issue is this?

#1007 Updated by Constantin Asofiei over 8 years ago

Hynek Cihlar wrote:

  1. test/fix window resize in Web

What kind of issue is this?

Window resize in browse/javascript client.

#1008 Updated by Hynek Cihlar over 8 years ago

Constantin Asofiei wrote:

Hynek Cihlar wrote:

  1. test/fix window resize in Web

What kind of issue is this?

Window resize in browse/javascript client.

Please keep me posted if you find anything, I have done some changes in the resize stack.

#1009 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811q Revision 10954

This is really good. Some thoughts:

1. When you add more details (extra is non-empty) to the drawing operations logging, please also add the equivalent in EmulatedWindowState.drawOnScreenBitmap() so that the Java and JS logs can easily be compared.

2. I think returning immediately in the fill case of drawRect() is incorrect. This bypasses the calls to drawLine() below but we must overdraw the outermost pixels of the filled rectangle otherwise it will have a fuzzy anti-aliased appearance. I understand that you don't want to stroke them with the current stroke. I agree with that. But we must overdraw with the default stroke and the passed in color.

3. I think we can make the passing of paths easier/cleaner to read. Instead of this:

         // now overdraw the stroked portion to eliminate anti-aliasing, we draw in a
         // clockwise direction (since we are not using paths, this is not strictly necessary)
         var path = this.drawLine(ctx, x, y, x + width - inset, y, color);

         var more = this.drawLine(ctx, x + width - inset, y + 1, x + width - inset, y + height - inset, color);
         Array.prototype.push.apply(path, more);

         more = this.drawLine(ctx, x + width - 1 - inset, y + height - inset, x + 1, y + height - inset, color);
         Array.prototype.push.apply(path, more);

How about this:

         var path = [];

         // now overdraw the stroked portion to eliminate anti-aliasing, we draw in a
         // clockwise direction (since we are not using paths, this is not strictly necessary)
         this.drawLine(ctx, x, y, x + width - inset, y, color, path);
         this.drawLine(ctx, x + width - inset, y + 1, x + width - inset, y + height - inset, color, path);
         this.drawLine(ctx, x + width - 1 - inset, y + height - inset, x + 1, y + height - inset, color, path);

The drawLine() code can just add to the same array and the code seems easier to read. After all of the calls are made, we still have access to the finished path and can stoke it as needed.

#1010 Updated by Sergey Ivanovskiy over 8 years ago

Greg, please review the committed revision 10955, added new parameter path to drawLine, but with help of function drawLineSegment that is without path in its parameters.

#1011 Updated by Greg Shah over 8 years ago

are we sure we need a hard-coded 96 DPI for the web driver? Because setting it to 72 produces the same drawn string, in Windows/Firefox. So I wonder if 96DPI is not something CSS specific, and Canvas text drawing is more related to the OS DPI, not CSS.

I don't have the complete answer here, but at least I can share some thoughts.

1. We know that the CSS pixel size is designed to be 96 DPI, and it does not change.

2. We know that the actual resolution of the output device (the screen in this case), can be different than 96 DPI. If it is different, this means that the browser must scale the canvas output (96 DPI) to the device output DPI.

3. The browser's window.devicePixelRatio value is supposed to report the scaling factor that is being used to translate the canvas output to device output.

4. On my Ubuntu Laptop using Firefox, this is reported as 1. The browser is thus reporting that no scaling should occur. My laptop actually has a screen resolution of 1600x900 which at 96 DPI, would suggest that my screen dimensions are 16.667x9.375 inches. In actuality, the screen is approx. 13.5x7.5 inches. Using the attached testcase, I drew a 96px by 96px square on a canvas. Then I used a ruler to measure the actual size on my screen: it was 13/16" and not 1 inch. This ratio makes sense since it is a pretty direct scaling of reported to actual screen sizes (13/16 ~= 13.5/16.667). I don't know exactly why the browser reports 96 DPI (which is not really correct). But interestingly, Java's Toolkit.getDefaultToolkit().getScreenResolution(); reports 96 as well. Using our CSS pixel drawing gives a result that is exactly (pixel by pixel) the same as is generated by Java2D. Based on my calculations, this seems to map pretty closely with the device pixels even if the reported DPI is off in both JS and Java.

5. When there is scaling going on, we can have a "high DPI" rendering problem which can make images and text look blurry. The most common case for this high DPI output to be present is on mobile devices like phones or tablets, which typically have much higher resolution screens than do standard desktop systems. At this point, I don't see a visual need to handle high DPI on normal desktops/laptops. Although there is some kind of mismatched DPI, the visual result is acceptable. However, for mobile devices we will need to implement this properly. Some useful links are below.

6. To try to detect if the text is not directly mapping to device pixels, I added text in "96px sans-serif" to the testcase. Then I used the ps.fonts.js textHeight() function to measure it (72px). Here is a capture of the results:

The 96px text does not measure as 96px in height, presumably because no single character in the font is meant to fill the entire 96px height. There is always an ascent and descent from the baseline and then there is space such that the bounding box can be 96px high and the text will look OK.

Interestingly, the Java2D version displays nearly identical text sizes BUT the metrics reported are very different (113px).

It seems to me that it is our JS metrics code that is causing us to scale things. Perhaps I am misunderstanding the code, but the text height calculation is so different while the text visually is almost identical, so that is my guess.

Some good links about high DPI scaling:

http://www.html5rocks.com/en/tutorials/canvas/hidpi/ (be careful with this one, the backingStorePixelRatio value no longer is supported in any modern browser, so all his code will work as if that were set to 1)
http://searchvoidstar.tumblr.com/post/86542847038/high-dpi-rendering-on-html5-canvas-some-problems#_=_
https://code.google.com/p/chromium/issues/detail?id=277205
http://stackoverflow.com/questions/24332639/why-context2d-backingstorepixelratio-deprecated
http://stackoverflow.com/questions/15661339/how-do-i-fix-blurry-text-in-my-html5-canvas
http://stackoverflow.com/questions/27382331/how-a-css-pixel-size-is-calculated
http://stackoverflow.com/questions/14488849/higher-dpi-graphics-with-html5-canvas (the changeResolution() example is a short yet useful summary of the technique)
https://gist.github.com/joubertnel/870190
http://jsfiddle.net/4JH75/19/ (this one will show the same text when not in high DPI mode and on a high DPI device the text will look different)

#1012 Updated by Greg Shah over 8 years ago

And the attachments...

#1013 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811q Revision 10955

Good changes.

#1014 Updated by Greg Shah over 8 years ago

Wow, we are very code to having the hello world program pixel-for-pixel identical with the Swing version:

The following problems remain:

1. Font scaling issues. Constantin has this one.

2. Close button still has some slight corruption. Sergey has this one.

3. The message area grey background sticks out 1 more pixel in the Swing version while it doesn't stick out in the JS version. I'll take this one.

#1015 Updated by Greg Shah over 8 years ago

Constantin, I'd like you to tackle the following (in this priority order, unless you have reasons that a different order is better):

1. JS font sizing problem.

2. Implement a JS equivalent to the Swing WindowFocusListener which generates a WindowActivated event on focus lost or focus gained. I think the hit-testing idea should work here. It isn't clear to me why we only conditionally process the WindowActivated event when we lose focus. I'm not sure if there is something else needed in our JS implementation here.

3. Implement a JS equivalent to the Swing usage of WindowListener (for iconified and deiconified) and WindowStateListener (for maximized).

4. Implement a JS equivalent to the SwingEmulatedWindow.enableEvents(boolean).

5. window resize: the cursor is not restored in Swing

6. test/fix window resize in Web

7. specify mouse actions for remaining widgets (and test)

#1016 Updated by Greg Shah over 8 years ago

Sergey: after your work on the bug number 2 from note 1014, please work on the following.

1. Implement the GuiWebEmulatedWindow.setIconificationState() which will certainly need to call down to the client to change the visibility of the affected window.

2. Modify the generic GUI client code to add a GuiDriver.setIcon() method. Add a PaintPrimitives type for SET_ICON and AbstractGuiDriver can add it to the drawing ops queue. Implement the feature in both the Swing client and in the JS client. The idea is that this is similar to the SET_TITLE. It is being done for the taskbar support, we aren't actually drawing the title or icon in the SET_TITLE or SET_ICON operations. in Swing, it will need to setIconImage() for the containing window. The intention is to ensure that the OS taskbar will display the icon instead of the generic Java Duke icon. In JS, we should save the bitmap data as an icon member of the Window object. Don't worry about drawing it yet (that is the next task).

3. Implement our own equivalent of the windows taskbar. This would be a "docked" tray at the bottom of the browser window. Each Window object will have a button in the taskbar that will allow switching between the windows and restoring (de-iconification) of an iconified window. Each button should have the icon and the window title displayed. The currently active window should be distinguished from the inactive windows. The events generated by user mouse clicks in this taskbar will need to integrate with the iconify/deiconify hooks that Constantin is building.

#1017 Updated by Sergey Ivanovskiy over 8 years ago

Committed revision 10956 should fix the close button issue (#2) related to FILL_RECT drawing operation. Clean up the code, committed revision 10957.

#1018 Updated by Constantin Asofiei over 8 years ago

Greg, about the fonts.
  1. bold fonts require their own .ttf file - so I've changed the custom fonts to specify if the font is bold or not, and load the proper definition, when defining the font.
  2. when scaling the font, this is done using the destination DPI; legacy/current metrics are used just to decide if scaling is needed or not - they are not involved during actual font scaling. The font scaling formula is:
    new-font-point-size = font-point-size * destination-DPI / 72
    

    This result is then set as the font's new point size; internally, Swing names the parameter as "factor", but it just replaces the font's point size with the specified value. I'm doing the same in JS - specify new point size for the font.
    The above formula is AWT specific; from here: http://www.3rd-evolution.de/tkrammer/docs/java_font_size.html "Java assumes 72 DPI screen resolution". But the drawing for the Web client is not done by Java! I think we need an API to return the scale factor: so for Swing, return destination-DPI / 72 and for Web return window.devicePixelRatio. And the formula will be:
    new-font-point-size = font-point-size * scale-factor
    
  3. with the above changes, using a Tahoma default-font (not bold) and a Tahoma font for the title (bold) produces this (left web, right swing). What I can't explain why there is some anti-aliasing on my system and not on your images.

#1019 Updated by Greg Shah over 8 years ago

Constantin: Good work. Please check in your changes.

In addition to the unwanted anti-aliasing, it also seems that the slight sizing differences between web and Swing versions seem to cause the web version to display the text slightly above and to the right of where the Swing client draws the text. Or perhaps there is a different cause.

The frame title font also seems less bold in the web version.

Regardless, we will need to fix all 3 of those issues, but that can wait until later. I'll put them on the master list.

For now it is best for you to move on to the listeners work.

#1020 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

Constantin: Good work. Please check in your changes.

1811q rev 10958 contains the fix for the font scaling using DPI and bold font support. I've changed the testcases/simple/server/directory.xml to add Tahoma bold - the ttf file is in testcases/simple/server/fonts/tahomabd.ttf.

In addition to the unwanted anti-aliasing, it also seems that the slight sizing differences between web and Swing versions seem to cause the web version to display the text slightly above and to the right of where the Swing client draws the text. Or perhaps there is a different cause.

This is related to how text height is computed I think... the ascent/descent problem. Currently, I'm forcing the same text height for all cases, by adding the '\u2503' (vertical bar) to it. We need a smarter way to compute this.

The frame title font also seems less bold in the web version.

I think it looks less bold because of the anti-aliasing.

For now it is best for you to move on to the listeners work.

OK.

#1021 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811q Revision 10957

I'm good with the changes.

#1022 Updated by Greg Shah over 8 years ago

Sergey: the drawing of the X in the close button is quite different from the Swing version. Would you please diagnose and resolve that?

#1023 Updated by Sergey Ivanovskiy over 8 years ago

Sergey: the drawing of the X in the close button is quite different from the Swing version. Would you please diagnose and resolve that?

Yes, the / part is 1 pixel wider due to the line stroke with 2 pixel width. Does you mean this difference? Using the logs of close CaptionButton I got the script drawing and found that / is drawn by two parallel lines having the 2 pixels width. If the coordinate a properly translated we have \ corresponds to the segment drawLineSegment(temp.ctx, 4, 3, 15, 14, temp.rawColor)
but / - to two segments drawLineSegment(temp.ctx, 4, 14, 15, 3, temp.rawColor) and drawLineSegment(temp.ctx, 5, 14, 16, 3, temp.rawColor).
I don't know how to resolve it because for the given stroke it is properly displayed.

#1024 Updated by Sergey Ivanovskiy over 8 years ago

Also, these logs of swing client are proved it:
PaintPrimitives.TRANSLATE_POP 1; x = -358; y = -4
PaintPrimitives.TRANSLATE_PUSH 2; x = 379; y = 4
PaintPrimitives.CLIP 3; x = 0; y = 0; width = 20; height = 18
PaintPrimitives.SET_COLOR r = 240; g = 240; b = 240
PaintPrimitives.FILL_3D_RECT x = 0; y = 0; width = 19; height = 17; raised = true
PaintPrimitives.SET_COLOR r = 0; g = 0; b = 0
PaintPrimitives.SET_LINE_STROKE strokeStyleId = 1; strokeWidth = 2
PaintPrimitives.DRAW_LINE x1 = 4; y1 = 3; x2 = 15; y2 = 14
PaintPrimitives.DRAW_LINE x1 = 4; y1 = 14; x2 = 15; y2 = 3
PaintPrimitives.DRAW_LINE x1 = 5; y1 = 14; x2 = 16; y2 = 3
PaintPrimitives.SET_COLOR r = 240; g = 240; b = 240
PaintPrimitives.FILL_RECT x = 2; y = 2; width = 15; height = 2
PaintPrimitives.FILL_RECT x = 2; y = 14; width = 15; height = 2
PaintPrimitives.SET_COLOR r = 0; g = 0; b = 0
PaintPrimitives.SET_LINE_STROKE strokeStyleId = 0; strokeWidth = 0

May be due to odd or even points coordinates two pixels width of two parallels are collapsed in java 2d?

#1025 Updated by Sergey Ivanovskiy over 8 years ago

I will try to fix it base on odd or even coordinates in p2j.strokes.js class WidenPathRenderer.

#1026 Updated by Greg Shah over 8 years ago

May be due to odd or even points coordinates two pixels width of two parallels are collapsed in java 2d?

Possibly. But it seems this is actually a problem in the Swing driver code. See if you can come up with a solution there and then you can clean up the CaptionButton.draw():

                  // north-west - south-east line
                  gd.drawLine(scaleX(4), scaleY(3), width - scaleX(4), height - scaleY(3));

                  // SW-NE line
                  gd.drawLine(scaleX(4), height - scaleY(3), width - scaleX(4), scaleY(3));

                  // TODO: the SW-NE line is painted with 1/2 thickness of the NW-SE line,
                  //       this unfortunately limits the scaling capabilities
                  gd.drawLine(scaleX(5), height - scaleY(3), width - scaleX(3), scaleY(3));

I will try to fix it base on odd or even coordinates in p2j.strokes.js class WidenPathRenderer.

I don't think the JS code is a problem.

#1027 Updated by Sergey Ivanovskiy over 8 years ago

Greg, please clarify the issue, we draw the cross with two pixels width, if so we can remove this
// TODO: the SW-NE line is painted with 1/2 thickness of the NW-SE line,
// this unfortunately limits the scaling capabilities
gd.drawLine(scaleX(5), height - scaleY(3), width - scaleX(3), scaleY(3));
or we should draw \ with 2-pixels but / with 3 -pixels?

#1028 Updated by Hynek Cihlar over 8 years ago

Sergey Ivanovskiy wrote:

Greg, please clarify the issue, we draw the cross with two pixels width, if so we can remove this
// TODO: the SW-NE line is painted with 1/2 thickness of the NW-SE line,
// this unfortunately limits the scaling capabilities
gd.drawLine(scaleX(5), height - scaleY(3), width - scaleX(3), scaleY(3));
or we should draw \ with 2-pixels but / with 3 -pixels?

The two drawLine calls for the SW-NE line are there as a workaround for a Swing? 2D rendering bug. I couldn't make it render the line the same way as the NW-SE line otherwise.

#1029 Updated by Sergey Ivanovskiy over 8 years ago

The two drawLine calls for the SW-NE line are there as a workaround for a Swing? 2D rendering bug. I couldn't make it render the line the same way as the NW-SE line otherwise.

We can simulate this behaviour in js for two nearest parallel lines to be 2-pixel width if its stroke width is also 2.

#1030 Updated by Greg Shah over 8 years ago

We can simulate this behaviour in js for two nearest parallel lines to be 2-pixel width if its stroke width is also 2.

I really don't want to duplicate a Java2D bug in JS. Please see if you can determine why the Java2D implementation does this. It relates to the coordinates, the direction of drawing, a rounding problem... or some other element that is present in the line-stroking version that draws a diagonal line in a SW to NE direction.

#1031 Updated by Greg Shah over 8 years ago

Task branch 1811q, revision 10959 has a fix for scrollable list overdrawing its boundaries when the right or bottom insets are non-zero. This was seen in the message area drawing 1 pixel too far to the right side of the background fill rectangle. For some reason it only manifested in the Swing client.

Hynek: please review my ScrollableListGuiImpl modification.

#1032 Updated by Hynek Cihlar over 8 years ago

Greg Shah wrote:

Task branch 1811q, revision 10959 has a fix for scrollable list overdrawing its boundaries when the right or bottom insets are non-zero. This was seen in the message area drawing 1 pixel too far to the right side of the background fill rectangle. For some reason it only manifested in the Swing client.

Hynek: please review my ScrollableListGuiImpl modification.

The changes are ok. I am only concerned about the call to drawStringCentered() later in draw(). I think it may render the text into the border of the scrollable list. So there should be another clipping rectangle which would account for the insets.

#1033 Updated by Greg Shah over 8 years ago

How about changing this:

                     // draw item text
                     gd.translatePush(x, y);
                     String text = model().get(i);
                     if (text != null)
                     {
                        gd.drawStringCentered(text, nativeInsets.left, rowHeight - nativeInsets.top);
                     }
                     gd.translatePop();

to this:

                     // draw item text
                     String text = model().get(i);
                     if (text != null)
                     {
                        gd.translatePush(x, y);
                        gd.setDrawingArea(new NativeRectangle(0, 0, dy, dx));
                        gd.drawStringCentered(text, nativeInsets.left, rowHeight - nativeInsets.top);
                        gd.setDrawingArea(null);
                        gd.translatePop();
                     }

#1034 Updated by Hynek Cihlar over 8 years ago

Yes, that should do it. I would only prefer the GuiDriver.draw method.

#1035 Updated by Greg Shah over 8 years ago

Good idea. I'm going with this:

                     final String text = model().get(i);

                     // draw item text
                     if (text != null)
                     {
                        NativePoint     pt = new NativePoint(x, y);
                        NativeRectangle cl = new NativeRectangle(0, 0, dy, dx); 

                        gd.draw(pt, cl, new Runnable()
                        {
                           @Override
                           public void run()
                           {
                              gd.drawStringCentered(text,
                                                    nativeInsets.left,
                                                    rowHeight - nativeInsets.top);
                           }
                        });
                     }

#1036 Updated by Sergey Ivanovskiy over 8 years ago

I really don't want to duplicate a Java2D bug in JS. Please see if you can determine why the Java2D implementation does this. It relates to the coordinates, the direction of drawing, a rounding problem... or some other element that is present in the line-stroking version that draws a diagonal line in a SW to NE direction.

PiscesRenderingEngine and Stroker classes handle the drawing lines according to a stroke width. All path widen calculations use Euclid distance and floating point operations, but then rounding results (Math.floor) inside PathIteator (NormalizingPathIterator) and this incorrect shape is drawn by underlined X11 graphics. The decimal parts of BasicStroke width can influence drawing, g2.getStroke().createStrokedShape also produces different results for the cross sides.
g2.setStroke(new BasicStroke(2));
g2.translate(250, 450);
//g2.drawLine(4, 3, 15, 14);
Line2D.Float l1 = new Line2D.Float(4, 3, 15, 14);
g2.draw(g2.getStroke().createStrokedShape(l1));
Line2D.Float l2 = new Line2D.Float(4, 14, 15, 3);
//g2.getStroke().createStrokedShape(p2);
g2.draw(g2.getStroke().createStrokedShape(l2));

#1037 Updated by Greg Shah over 8 years ago

Is there any performance or functional disadvantage to switching the g2.drawLine() to g2.getStroke().createStrokedShape() in our Swing DRAW_LINE implementation?

I haven't made a standalone testcase to see the results of the change. I assume the result is correct.

#1038 Updated by Sergey Ivanovskiy over 8 years ago

No, it doesn't help.!strokesJava2DTest.png! The Java2d surprises me. It doesn't use symmetry of x and y.

#1039 Updated by Constantin Asofiei over 8 years ago

Greg, if we can't find a way to fix things in the Swing client, I think it's a good idea to move the Caption button drawing code at the driver level: use some high-level drawing primitives like DRAW_CAPTION_MAX, DRAW_CAPTION_MIN, etc, and implement the drawing at the Swing drivers and the p2j.screen JS drive.

#1040 Updated by Constantin Asofiei over 8 years ago

1811q rev 10961 contains WindowActivated support for the JS (windows can be used in the Web client now).

#1041 Updated by Sergey Ivanovskiy over 8 years ago

Greg, please review my finding if turn on antialiasing before '/' drawing and turn off after it, then the final / line looks like the '\' line. We can add ANTIALIAS to drawing primitives.
//second successful
g2.setColor(Color.black);
g2.translate(50, 0);
g2.drawLine(4, 3, 15, 14);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.drawLine(4, 14, 15, 3);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
//g2.drawLine(5, 14, 16, 3);
g2.setColor(Color.white);
g2.fillRect(2, 2, 15, 2);
g2.fillRect(2, 14, 15, 2);

#1042 Updated by Constantin Asofiei over 8 years ago

Sergey Ivanovskiy wrote:

Greg, please review my finding if turn on antialiasing before '/' drawing and turn off after it, then the final / line looks like the '\' line. We can add ANTIALIAS to drawing primitives.

This is interesting, but if you look closely, the "/" has a gray pixel border on each side. So the real width of the line is not 3 pixels, but 5.

#1043 Updated by Greg Shah over 8 years ago

if we can't find a way to fix things in the Swing client, I think it's a good idea to move the Caption button drawing code at the driver level: use some high-level drawing primitives like DRAW_CAPTION_MAX, DRAW_CAPTION_MIN, etc, and implement the drawing at the Swing drivers and the p2j.screen JS drive.

Why not use images instead of vector drawing? We can just capture the results of the properly drawn X as an image and render that directly. With caching it should be reasonably fast and it is less complicated.

#1044 Updated by Sergey Ivanovskiy over 8 years ago

Greg, please approve I am going to add the cross.png image with the transparent background and then to change CaptionButton to draw 'x' as an image instead of two line segments. But the stroke color can't be changed for an image.

#1045 Updated by Sergey Ivanovskiy over 8 years ago

But the stroke color can't be changed for an image. We can draw line like you do it for js?

#1046 Updated by Hynek Cihlar over 8 years ago

Greg Shah wrote:

Why not use images instead of vector drawing?

The reason for vectors was to allow caption size scaling.

#1047 Updated by Sergey Ivanovskiy over 8 years ago

We can use drawString('x' .. if it looks like the cross.

#1048 Updated by Greg Shah over 8 years ago

The reason for vectors was to allow caption size scaling.

Good point, I had forgotten this.

I think it's a good idea to move the Caption button drawing code at the driver level: use some high-level drawing primitives like DRAW_CAPTION_MAX, DRAW_CAPTION_MIN, etc, and implement the drawing at the Swing drivers and the p2j.screen JS driver

My concern with this is that we are having to put theme-specific "primitives" into the driver to enable this. Any differences in the min/max/restore buttons between themes will need driver-level variants. We would have to inform the driver which variant to use. It is pretty messy.

#1049 Updated by Greg Shah over 8 years ago

We can use drawString('x' .. if it looks like the cross.

It must be exactly the same to work.

I'm not sure if that can be made to work at all scaling levels or for all theming differences.

#1050 Updated by Sergey Ivanovskiy over 8 years ago

This code draws the cross of 2 pixels width, we have two variants: to implement the custom line widening drawing primitive or
or try to use drawRect, but it needs to test the results?
@strokeManager.setLineStroke(g2, LineStroke.SPECIFIED_WIDTH, 2);
g2.setColor(Color.black);
g2.translate(0, 50);
g2.drawLine(4, 3, 15, 14);
strokeManager.setLineStroke(g2, LineStroke.SPECIFIED_WIDTH, 1);
//g2.drawLine(4, 14, 15, 3);
for (int i = 5, j = 14; i < 16; i++, j--) {
g2.fillRect(i - 1, j -1, 2, 2);
}
//g2.drawLine(5, 14, 16, 3);
g2.setColor(Color.white);
g2.fillRect(2, 2, 15, 2);
g2.fillRect(2, 14, 15, 2);
@

#1051 Updated by Greg Shah over 8 years ago

Another idea: we could implement transforms that rotate the \ and horizontally flip (mirror image) the pixels around the center line.

We would have to implement these as push/pop operations like our clipping and coordinate translation. The advantage is that we should be able to duplicate the / exactly.

#1052 Updated by Sergey Ivanovskiy over 8 years ago

Another idea: we could implement transforms that rotate the \ and horizontally flip (mirror image) the pixels around the center line.

We would have to implement these as push/pop operations like our clipping and coordinate translation. The advantage is that we should be able to duplicate the / exactly.

It doesn't work I checked it in my test #1811-1041.

#1053 Updated by Hynek Cihlar over 8 years ago

How about a different version of JRE?

#1054 Updated by Hynek Cihlar over 8 years ago

Sergey, have you tried changing the KEY_STROKE_CONTROL hint? See http://docs.oracle.com/javase/7/docs/api/java/awt/RenderingHints.html#KEY_STROKE_CONTROL.

#1055 Updated by Sergey Ivanovskiy over 8 years ago

Sergey, have you tried changing the KEY_STROKE_CONTROL hint? See http://docs.oracle.com/javase/7/docs/api/java/awt/RenderingHints.html#KEY_STROKE_CONTROL.

No, I propose to implement our custom widen line algorithm, that is to draw squared rectangles of the specified stroke width that cover our line segment.

How about a different version of JRE?

Yes, drawing implementation can be different for oracle and for open jdk.

#1056 Updated by Hynek Cihlar over 8 years ago

Setting RenderingHints.KEY_STROKE_CONTROL to RenderingHints.VALUE_STROKE_PURE does resolve the problem.

#1057 Updated by Sergey Ivanovskiy over 8 years ago

Setting RenderingHints.KEY_STROKE_CONTROL to RenderingHints.VALUE_STROKE_PURE does resolve the problem.

I will check it and it is good to implement renderingHint it can be used in the future may be with antialias on, :)
But as an alternative we can change the code for close button to:
@ case CLOSE:
gd.setLineStroke(LineStroke.SPECIFIED_WIDTH, scaleX(2));

// north-west - south-east line
gd.drawLine(scaleX(4), scaleY(3), width - scaleX(4), height - scaleY(3));
// SW-NE line
//gd.drawLine(scaleX(4), height - scaleY(3), width - scaleX(4), scaleY(3));
gd.setLineStroke(LineStroke.SPECIFIED_WIDTH, scaleX(1));
// TODO: the SW-NE line is painted with 1/2 thickness of the NW-SE line,
// this unfortunately limits the scaling capabilities
gd.drawLine(scaleX(4), height - scaleY(3), width - scaleX(4), scaleY(3));
int extent = scaleX(2);
int shift = extent >> 1;
for (int x = scaleX(5), y = height - scaleY(3); x <= width - scaleX(4); x++, y--) {
gd.fillRect(x - shift, y - shift, extent, extent);
}
// these rects make the proper line ends
gd.setColor(faceColor);
gd.fillRect(2, 2, width - 4, scaleY(2));
gd.fillRect(2, height - 3, width - 4, scaleY(2));
gd.setColor(gd.getSysColor(SysColor.COLOR_BTNTEXT));
gd.setLineStroke(LineStroke.DEFAULT);
@
and it works the same for gui swing and web client. What do you think?

#1058 Updated by Hynek Cihlar over 8 years ago

When RenderingHints.KEY_STROKE_CONTROL set to RenderingHints.VALUE_STROKE_PURE, the following produces the expected result.

                  // north-west - south-east line
                  gd.drawLine(scaleX(4), scaleY(3), width - scaleX(5), height - scaleY(4));

                  // SW-NE line
                  gd.drawLine(scaleX(5), height - scaleY(4), width - scaleX(4), scaleY(3));

                  // these rects make the proper line ends
                  gd.setColor(faceColor);
                  gd.fillRect(2, 2, width - 4, scaleY(2));  
                  gd.fillRect(2, height - 4, width - 4, scaleY(2));
                  gd.setColor(gd.getSysColor(SysColor.COLOR_BTNTEXT));

#1059 Updated by Sergey Ivanovskiy over 8 years ago

Greg, we would like to add setRenderingHints to drawing primitives.

#1060 Updated by Greg Shah over 8 years ago

we would like to add setRenderingHints to drawing primitives.

Yes, go ahead.

Sergey: please also note that in Redmine the @ signs can only be used on a single line. For multi-line code examples, please use .

#1061 Updated by Greg Shah over 8 years ago

How about a different version of JRE?

This would restrict customers (and ourselves) too much to be acceptable. I'm glad another solution was found.

#1062 Updated by Sergey Ivanovskiy over 8 years ago

How about a different version of JRE?

This would restrict customers (and ourselves) too much to be acceptable. I'm glad another solution was found.

The tested java version is
java version "1.7.0_79"
OpenJDK Runtime Environment (IcedTea 2.5.6) (7u79-2.5.6-0ubuntu1.15.04.1)
OpenJDK 64-Bit Server VM (build 24.79-b02, mixed mode)
and your solution is working. As an alternative we can use CaptionButton_diff.txt.
But I think that it would be good if we have setRenderingHint implementation for stroke and for antialiasing?

#1064 Updated by Greg Shah over 8 years ago

But I think that it would be good if we have setRenderingHint implementation for stroke and for antialiasing?

We definitely need it for RenderingHints.VALUE_STROKE_PURE. Where would we use the anti-aliasing support?

#1065 Updated by Sergey Ivanovskiy over 8 years ago

But I think that it would be good if we have setRenderingHint implementation for stroke and for antialiasing?

We definitely need it for RenderingHints.VALUE_STROKE_PURE. Where would we use the anti-aliasing support?

I only mean to implement this support just in case. Going to implement only RenderingHints.KEY_STROKE_CONTROL

#1066 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811q Revision 10961

The changes look good.

#1067 Updated by Constantin Asofiei over 8 years ago

1811q revision 10926 contains support for window iconification/deiconification, maximize and close, plus enableEvents. There is still a issue which ends up with out of order translate pops, working on it.

#1068 Updated by Sergey Ivanovskiy over 8 years ago

Greg, Hynec, please look at these difficulties that comes trying to synchronize the close button with swing client and web client. I think the problem there exists to draw the swing close button correctly it is used the next line under diagonal /, and if we draw the diagonal / with STROKE_PURE the close button for swing looks unsymmetrical. With web client there is the opposite situation if we draw the diagonal / with 2 pixels stroke it looks symmetrical and correct, but if we use the next line under the diagonal it is unsymmetrical.
Let us translate to point (4, 3). Then we must draw diagonals for this square (0,0) (0, 11) (11, 11) (11, 0)
// PaintPrimitives.TRANSLATE_POP 1; x = -358; y = -4
// PaintPrimitives.TRANSLATE_PUSH 2; x = 379; y = 4
// PaintPrimitives.CLIP 3; x = 0; y = 0; width = 20; height = 18
// PaintPrimitives.SET_COLOR r = 240; g = 240; b = 240
// PaintPrimitives.FILL_3D_RECT x = 0; y = 0; width = 19; height = 17; raised = true
// PaintPrimitives.SET_COLOR r = 0; g = 0; b = 0
// PaintPrimitives.SET_LINE_STROKE strokeStyleId = 1; strokeWidth = 2
// PaintPrimitives.DRAW_LINE x1 = 4; y1 = 3; x2 = 15; y2 = 14 it is diagonal!!!

// PaintPrimitives.DRAW_LINE x1 = 4; y1 = 14; x2 = 15; y2 = 3 it is diagonal !!!

// PaintPrimitives.DRAW_LINE x1 = 5; y1 = 14; x2 = 16; y2 = 3 it is the next line
// PaintPrimitives.SET_COLOR r = 240; g = 240; b = 240
// PaintPrimitives.FILL_RECT x = 2; y = 2; width = 15; height = 2
// PaintPrimitives.FILL_RECT x = 2; y = 14; width = 15; height = 2
// PaintPrimitives.SET_COLOR r = 0; g = 0; b = 0
// PaintPrimitives.SET_LINE_STROKE strokeStyleId = 0; strokeWidth = 0

I have already implemented setGuiRenderingHints but it follows that STROKE_PURE produces incorrect graphics. I think I should use the code from #1811-1062

#1069 Updated by Greg Shah over 8 years ago

I have already implemented setGuiRenderingHints but it follows that STOKE_PURE produces incorrect graphics. I think I should use the code from #1811-1062

OK, go ahead.

#1070 Updated by Sergey Ivanovskiy over 8 years ago

Committed revision 10963. It should fix close button differences for web and swing clients. Now the logs for drawing close button is changed

PaintPrimitives.TRANSLATE_POP 1; x = -358; y = -4
PaintPrimitives.TRANSLATE_PUSH 2; x = 379; y = 4
PaintPrimitives.CLIP 3; x = 0; y = 0; width = 20; height = 18
PaintPrimitives.SET_COLOR r = 240; g = 240; b = 240
PaintPrimitives.FILL_3D_RECT x = 0; y = 0; width = 19; height = 17; raised = true
PaintPrimitives.SET_COLOR r = 0; g = 0; b = 0
PaintPrimitives.SET_LINE_STROKE strokeStyleId = 1; strokeWidth = 2
PaintPrimitives.DRAW_LINE x1 = 4; y1 = 3; x2 = 15; y2 = 14
PaintPrimitives.SET_LINE_STROKE strokeStyleId = 1; strokeWidth = 1
PaintPrimitives.DRAW_LINE x1 = 4; y1 = 14; x2 = 15; y2 = 3
PaintPrimitives.FILL_RECT x = 4; y = 13; width = 2; height = 2
PaintPrimitives.FILL_RECT x = 5; y = 12; width = 2; height = 2
PaintPrimitives.FILL_RECT x = 6; y = 11; width = 2; height = 2
PaintPrimitives.FILL_RECT x = 7; y = 10; width = 2; height = 2
PaintPrimitives.FILL_RECT x = 8; y = 9; width = 2; height = 2
PaintPrimitives.FILL_RECT x = 9; y = 8; width = 2; height = 2
PaintPrimitives.FILL_RECT x = 10; y = 7; width = 2; height = 2
PaintPrimitives.FILL_RECT x = 11; y = 6; width = 2; height = 2
PaintPrimitives.FILL_RECT x = 12; y = 5; width = 2; height = 2
PaintPrimitives.FILL_RECT x = 13; y = 4; width = 2; height = 2
PaintPrimitives.FILL_RECT x = 14; y = 3; width = 2; height = 2
PaintPrimitives.SET_COLOR r = 240; g = 240; b = 240
PaintPrimitives.FILL_RECT x = 2; y = 2; width = 15; height = 2
PaintPrimitives.FILL_RECT x = 2; y = 14; width = 15; height = 2
PaintPrimitives.SET_COLOR r = 0; g = 0; b = 0
PaintPrimitives.SET_LINE_STROKE strokeStyleId = 0; strokeWidth = 0

#1071 Updated by Sergey Ivanovskiy over 8 years ago

gui&webClients.png

#1072 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811q Revision 10962

It is quite good. A couple of thoughts:

1. The WindowClose, WindowIconify and WindowMaximize functions in p2j.mouse.js all share mostly common structure. Only the core logic that occurs after a hit is detected differs between the 3 functions. This is a perfect candidate for implementing a single function and passing the differential logic as a closure.

2. The system menu is no longer available. I'm not sure if it was this revision or another recent one, but it must be recent because I remember opening it last week some time. I was going to try it to see if the menu items worked.

#1073 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811q Revision 10963

This is good. Please add a comment to the draw() code where you removed the TODO. The comment should explain why this approach is needed. It should also explain that this approach uses generic drawing primitives and yields the same result in both the web and Swing. Put in enough detail for the reader to understand the cause of the problem and the various approaches that were tried and which failed.

#1074 Updated by Greg Shah over 8 years ago

Rebased task branch 1811q from P2J trunk revision 10930. The latest branch revision is 10972.

#1075 Updated by Greg Shah over 8 years ago

I just committed some merge fixes in rev 10973.

Constantin: would you please check FontTable to make sure I didn't break anything?

#1076 Updated by Sergey Ivanovskiy over 8 years ago

Committed revision 10974.

#1077 Updated by Sergey Ivanovskiy over 8 years ago

Constantin please look at this issue

1. Implement the GuiWebEmulatedWindow.setIconificationState() which will certainly need to call down to the client to change the visibility of the affected window.

Now SET_ICONIFICATION_STATE can hold only 'iconify' boolean flag, actually a window can be in one of these states
  1. NORMAL
  2. ICONIFIED
  3. MAXIMIZED

I would like to name this message SET_WINDOW_STATE with all extended states. It gives possibility to support extended window states.

#1078 Updated by Constantin Asofiei over 8 years ago

Sergey Ivanovskiy wrote:

Constantin please look at this issue

1. Implement the GuiWebEmulatedWindow.setIconificationState() which will certainly need to call down to the client to change the visibility of the affected window.

Now SET_ICONIFICATION_STATE can hold only 'iconify' boolean flag, actually a window can be in one of these states
  1. NORMAL
  2. ICONIFIED
  3. MAXIMIZED

I would like to name this message SET_WINDOW_STATE with all extended states. It gives possibility to support extended window states.

I'm aware of this. The question is: do you plan to set the window state by other means than the window's caption buttons? Currently the MAXIMIZED/NORMAL states get set when pressing the Maximize caption button - this sends the "click" to the Java side and this in turn processes the window maximize/restore. So, are there are other means to change the window state?

#1079 Updated by Sergey Ivanovskiy over 8 years ago

I'm aware of this. The question is: do you plan to set the window state by other means than the window's caption buttons? Currently the MAXIMIZED/NORMAL states get set when pressing the Maximize caption button - this sends the "click" to the Java side and this in turn processes the window maximize/restore. So, are there are other means to change the window state?

No, it is clear now, the question is only due to that setExtendedState of java.awt.Frame is used as my starting view point.

#1080 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

I just committed some merge fixes in rev 10973.

Constantin: would you please check FontTable to make sure I didn't break anything?

The change is OK.

#1081 Updated by Sergey Ivanovskiy over 8 years ago

Greg, do we need to reuse

/** Loaded images map */
var loadedImages = new Map();

for SET_ICON?

#1082 Updated by Greg Shah over 8 years ago

for SET_ICON?

Yes, this seems reasonable. If we have an image ID, we can easily draw the icon from that alone, I suppose.

#1083 Updated by Sergey Ivanovskiy over 8 years ago

Greg, please review the committed revision 10975. It is the SET_ICON implementation.

#1084 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811q Revision 10975

The changes look good. The only thing that seems missing would be changes to WindowTitleBar to call GuiDriver.setIcon().

#1085 Updated by Constantin Asofiei over 8 years ago

1811q rev 10977 contains fixes related to p2j.mouse (window events functions are refactored to use a worker) and a fix for mouse: mouse capture state must be kept global, not per window - this is the main reason why I was seeing intermittent issues, mouse events were being processed by MouseHandler as for one of the two windows the state was on, not off. Also, I protected the selectWindow so that no other window can be selected if a batch drawing has started.

I still have to fix the system menu + for some reason the web client is not very responsive when pressing the maximize button.

#1086 Updated by Sergey Ivanovskiy over 8 years ago

Committed revision 10978. The changes are not good due to setIcon has the drawImage signature. It is because the web client draws images on the server side and pushes them on the canvas.

#1087 Updated by Greg Shah over 8 years ago

Remaining Task List (in priority order)

Task Branch 1811q

  1. fix system menu (CA)
  2. pressing maximize button makes client unresponsive (CA)
  3. window resize: the cursor is not restored in Swing (CA)
  4. test/fix window resize in Web (postponed to branch 1811r)
  5. specify mouse actions for remaining widgets and test (CA)
  6. WindowTitleBar setIcon() usage (SBI)
  7. implement windows taskbar equivalent (SBI)
  8. key processing differences for GUI, part 1 (SBI)
  9. regression test standard GUI testcases and resolve issues (GES)

Task Branch 1811r

  1. key processing differences for GUI, part 2 (SBI)
  2. EWS.resize() deadlocks (CA)
  3. frame title looks less bold, probably due to the anti-aliasing (CA)
  4. text positioning is slightly above and to the right compared to Swing client (CA)
  5. web client support for font aliases (CA)
  6. web client support for underline fonts (CA)

Task Branch 1811s

  1. test/fix window resize in Web (CA)
  2. match the 4GL fonts (esp bitmap fonts) to the Java and browser fonts (CA)
  3. clipboard issues (SBI)
  4. XOR compositing and DOTTED_LARGE line stroke, must follow the 2564a merge to trunk (SBI)
  5. moveToTop()/moveToBottom() driver workers to allow WindowManager to force specific changes (GES)

Task Branch 1811t

  1. performance improvements #2672 (CA)
  2. performance lag between pressing keys and seeing results drawn in web client (CA)
  3. rough cursor movement when using left/right keys in editor #2769
  4. iframe embedding support #1774 (GES/SBI)
  5. non_localhost testing #2928 (SBI)
  6. chrome testing #2929 (SBI)
  7. IE testing #2930 (SBI)
  8. web socket message sizes are limited to 32K, make sure that drawing ops and images larger than this are handled properly #2967 (SBI)
  9. disconnect/reconnect, manual reload must re-establish the websocket connection, needs to update the websock references in all GuiWebEmulatedWindows #2968 (SBI)
  10. eliminate the idle timeout so long as a keep-alive sequence occurs, otherwise a long-lived session will by automatically killed after the websocket timer pops (load an application into the web client and then just let it sit there, it will disconnect automatically when the timeout occurs) #2969 (SBI)

Task Branch 1811u

  1. customer app integration api #1774 (CA)
  2. embedded browser launch #1815 (SBI)

Task Branch 1811v

  1. safari testing #2931 (SBI)
  2. web client skinning #1826
  3. font anti-aliasing, if needed
  4. high resolution display scaling
  5. security code review
  6. investigate JS object sealing and other potentially good practices
  7. document the HTML/CSS/JS features in use and the minimum browser levels needed for support #2674
  8. document any limitations/differences from the original Windows environment #2675
  9. better JS logging API, including an sprintf() or String.format() implementation

Future Items

  1. reverse proxy #2683
  2. allow multiple P2J client sessions from the same virtual desktop (this enables use cases where the customer needs multiple Progress apps to run at the same time instead of having everything in one app)
  3. upgrade Jetty from 9.1.2 v20140210 to the latest stable version
  4. interactive child process launching in GUI, console/command line programs only
  5. eliminate DOJO usage from the GUI web client
  6. sending text to the web client is not safe when characters have greater than 2-byte Unicode encoding
  7. ChuiWebSimulator.setCursor() is unsafe for X and Y positions greater than 255
  8. login page improvements #2671
  9. remaining virtual desktop features #2673
  10. draw highlights/clear highlights drawing ops
  11. taskbar enabled/disabled via directory, defaults to enabled

#1088 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811q Revision 10977

The changes are good.

#1089 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811q Revision 10978

I have one concern. It is possible to load a .ico file as an image. I think this is possible at any time, not just for use as a window icon. For example, these can be used for button images. ImageGuiImpl.loadImage() will set isIcon to true if the filename ends in .ico. But in this case, ImageGuiImpl.drawImage() will call setIcon() for the window, and that would be incorrect. I think we need to ensure that only the usage from WindowTitleBar.WindowIcon is a valid case for calling setIcon().

#1090 Updated by Sergey Ivanovskiy over 8 years ago

Code Review Task Branch 1811q Revision 10978

I have one concern. It is possible to load a .ico file as an image. I think this is possible at any time, not just for use as a window icon. For example, these can be used for button images. ImageGuiImpl.loadImage() will set isIcon to true if the filename ends in .ico. But in this case, ImageGuiImpl.drawImage() will call setIcon() for the window, and that would be incorrect. I think we need to ensure that only the usage from WindowTitleBar.WindowIcon is a valid case for calling setIcon().

Fixed in the committed revision 10979.

#1091 Updated by Sergey Ivanovskiy over 8 years ago

Greg, 10979 uses doLayout of WindowTitleBar, I would like to change it again to place setIcon() code to draw() method of WindowIcon class.

I agree, the doLayout() method is not the optimal location. But I don't want this added to every draw() call either. It only ever has to happen once per window's lifetime. Why not add it to the WindowIcon.setIconData()?

No, we can't do it because WindowIcon.setIconData() is called from WindowTitleBar constructor and the required geometry parameters are not calculated at this moment, it is possible after doLayout() is called or overriding draw().

#1092 Updated by Sergey Ivanovskiy over 8 years ago

Greg, please review the committed revision 10980.

#1093 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811q Revision 10980

The changes are good.

#1094 Updated by Sergey Ivanovskiy over 8 years ago

Greg, setIconImage doesn't change the swing client application icon for my Ubuntu task bar(like desktop),
please look at the similar issue
http://stackoverflow.com/questions/30716573/how-i-can-set-desktop-icon-of-my-java-application-in-windows7-8
Planning to fix it because this code works properly

            final JFrame frame = new JFrame("Graphics Operations");
            frame.setSize(800, 600);
            frame.setLocation(150, 150);
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            try
            {
               frame.setIconImage(ImageIO.read(new File("./gclogo.ico")));
            }
            catch (IOException e1)
            {
               e1.printStackTrace();
            }

#1095 Updated by Greg Shah over 8 years ago

Planning to fix it because this code works properly

OK, good.

#1096 Updated by Sergey Ivanovskiy over 8 years ago

Found that if frame.setIconImage() is called after the frame becomes visible, then the application task bar icon isn't changed. To fix this bug we can hide frame frame.setVisible(false) before calling frame.setIconImage() and then perform the deferred invocation of frame.setVisible(true) in new drawing. Please look at the attached test.

#1097 Updated by Greg Shah over 8 years ago

The test does not work for me. I uncommented the frame.setVisible(false) and tried it, but there is no difference. Perhaps I'm doing something wrong?

#1098 Updated by Sergey Ivanovskiy over 8 years ago

If the image is found on the path, then you can try to run it several times or set the breakpoint after on setVisible(true) and execute it manually. This test proves there exists new java bug. I am on the way but have no solution now.

#1099 Updated by Sergey Ivanovskiy over 8 years ago

May be task bar application window that represents the application icon is updated asynchronously on setVisible(true). If sleeps for a one second after setImageIcon and before setVisible(true) respectively, then this test becomes more successful.

#1100 Updated by Greg Shah over 8 years ago

Adding a sleep is not a solution we can use in the production code. For now, stop working on this problem. I'll open a separate task for it.

Just put a TODO into the code that explains that:

1. It doesn't work at this time due to what appears like a Java bug.
2. The only way it has been seen to work is: visible set false then setIconData() then sleep(1000) then visible set true.

#1101 Updated by Sergey Ivanovskiy over 8 years ago

Adding a sleep is not a solution we can use in the production code. For now, stop working on this problem. I'll open a separate task for it.

Just put a TODO into the code that explains that:

1. It doesn't work at this time due to what appears like a Java bug.
2. The only way it has been seen to work is: visible set false then setIconData() then sleep(1000) then visible set true.

The last version of SetIconImageTest seems to fix this bug, okay, I am doing TaskBarPanel for the web client.

#1102 Updated by Greg Shah over 8 years ago

The last version of SetIconImageTest seems to fix this bug

Sadly, the last version is still broken on my system.

#1103 Updated by Sergey Ivanovskiy over 8 years ago

Sadly, the last version is still broken on my system.

Your system is fast enough to eliminate the time to start new thread.

For our virtual desktop or task bar panel is it suitable to use https://developer.mozilla.org/en-US/demos/detail/jtop-desktop-interface ? I think that task bar panel is closely related to virtual desktop issue and it would be good to prepare all it in general. Because I need to design docked areas on the virtual desktop to place task panel and icons for the task panel to show a currently active window.

#1104 Updated by Greg Shah over 8 years ago

The project is definitely interesting.

Although the license listed on MDN is GPL, on github the actual license seems to be the MIT license. MIT licensing would be OK, but GPL would NOT be OK. It makes me a little nervous that someone coded GPL on the MDN site, but I think that as long as we got the code from github instead of the MDN download, it would be OK.

My larger concern is that this is a heavyweight framework with its own assumptions. This may lead to the following:

  • There is quite a bit of additional JS code to load and process. This will slow down our JS client, at least at load time and probably at runtime too. The framework is SVG based which means it is very intensive in use of the DOM. Having a large/complex DOM implementation is not good for performance. Our current JS approach intentionally minimizes DOM integration points.
  • Aside from the performance aspects there are significant concerns with how it will affect our core code. For example, we are creating and managing canvases as our primary window abstraction. Introducing an SVG based document environment underneath our windows could be troublesome. For example, our current z-order implementation is very simple and clean, because there are no visual DOM elements other than our canvases. Adding a complex SVG drawing layer requires that there is a substantial amount of visual DOM elements that will exist and be manipulated. This could interfere with our core functionality. Anything that makes it more complicated to get our code working will also make it more complicated to maintain and debug. For example, it will make our code more fragile when maintained (more likely to have bugs when we edit the code).
  • We manage all aspects of the browser experience like disabling history. This may be harder when adding a heavy framework that has its own requirements and assumptions.

On the positive side, it does have quite a bit of potential in providing an environment that looks attractive and polished. However we have no plans for using the icons/folders on a virtual desktop itself. My comments about a virtual desktop in note 1087 were more about the ability to configure the virtual size of the "screen", add wallpaper and to be able to scroll it cleanly so that windows that are on parts of the screen that are not visible or not fully visible can be made visible.

SO: what are the specific features of JTop that we will leverage which solve problems we would otherwise need to solve? It seems like there is no built-in taskbar in that project. So the features are more peripheral to our needs. Are those features worth the "cost"?

#1105 Updated by Sergey Ivanovskiy over 8 years ago

Agree with you if there are no plans to use folders and launch icons. Greg, please clarify functionalities of the task bar panel that should be implemented? From your task it follows
1) it holds all started application's windows as corresponding icons
2) if a window is active then its task bar icon has the selected border and the selected background.
3) it can be moved and attached to the provided docks on the visible screen (or virtual screen?)
4) it fills all maximum width or height if it is attached to the page's foot or to the pages' right side.(or maximum width of the virtual screen that can be greater than the browser's view?)
I will plan to do it within the current week, is it okay?

#1106 Updated by Greg Shah over 8 years ago

1) it holds all started application's windows as corresponding icons

Yes. But it should show both the icon and the window title.

2) if a window is active then its task bar icon has the selected border and the selected background.

Yes. One good way to do this is for each task to be a button, with the active one being a 3D button and the others being flat buttons. I am not suggesting using the HTML button elements. I think the taskbar is just a special canvas that we create and render. Each task just "acts like" a button.

3) it can be moved and attached to the provided docks on the visible screen (or virtual screen?)

For now, we only need to dock it at the bottom of the page. Let's not worry about docking to any other side.

4) it fills all maximum width or height if it is attached to the page's foot or to the pages' right side.(or maximum width of the virtual screen that can be greater than the browser's view?)

It should fit the maximum visible width of the page. So it is always visible, no matter how the virtual screen gets scrolled. I think the position:fixed style may be helpful here.

I will plan to do it within the current week, is it okay?

Good.

#1107 Updated by Constantin Asofiei over 8 years ago

Greg, 1811q rev 10981 replaces the window popup with the menu implementation in 1790. It's still buggy, but the bugs should be fixed in 1790e - I will re-check after 1790e gets merged to trunk.

#1108 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811q Revision 10981

This looks quite good.

In p2j.mouse.js, the MousePopupable() can probably be cut down to use windowStateChangedWorker(). windowStateChangedWorker() would need a minor change to the addEventListener() approach to allow the caller to specify more than one event. Otherwise it looks pretty much the same.

#1109 Updated by Sergey Ivanovskiy over 8 years ago

Constantin, could you help to explain the logic sequence from click on the mimimize caption button to CaptionButton.mouseClicked(MouseEvent e). For the current branch 1811q clicks on the window buttons are not handled.
1)

MouseHandler
public void mouseClicked(MouseEvent e)
   {
      if (state)
      {
         tc.postMouseEvent(e, ews.getWindowId());
      }
   }

#1110 Updated by Constantin Asofiei over 8 years ago

Sergey Ivanovskiy wrote:

Constantin, could you help to explain the logic sequence from click on the mimimize caption button to CaptionButton.mouseClicked(MouseEvent e). For the current branch 1811q clicks on the window buttons are not handled.

Yes, I know, clicking the buttons is broken, I'm working on this - I hope to have something tonight.

The sequence of calls is this: JS captures the click (in p2j.mouse:WindowIconify) and transfers it to WebMouseHandler.raiseEvent; from this is sent to MouseHandler.mouseClicked which posts it to the event queue, from which gets picked up and process by the ThinClient. After processing, WindowGuiImpl.iconify ends up being called, which in turn sends it to the GuiDriver.iconifyWindow.

For the Web driver, it will end up calling back to EMS.iconifyWindow, which calls GuiWebEmulatedWindow.setIconificationState - this will notify the JS to actually iconify the window. The JS code which will minimize the window at the JS driver is p2j.screen:Window.iconify and p2j.screen:Window.deiconify - this two APIs (not implemented yet) do the actual work, to show/hide the window at the JS screen.

So, please add the code which iconifies or deiconifies the window to iconify and deiconify. Note that the message which is currently in deiconify needs to be moved at the taskbar - the APIs don't need to send anything to the java side.

#1111 Updated by Constantin Asofiei over 8 years ago

1811q rev 10982 contains an improvement for web driver: all requests incoming from the JS are pushed and processed in a separate thread. The issue we are seeing (lag in mouse processing) breaks down to this:
  1. when the JS is sending data via websocket, the JS's thread is "busy" for the control to be received back from the remote side. I've solved this by using a dedicated thread where all incoming requests are pushed and processed, thus releasing the websocket, which in turn allows the JS thread to process events again. Otherwise, the JS thread is "paused" and it ignores any other events until it gets control back.
  2. when the JS is receiving data via websocket, again the JS thread is "busy" so no more events are captured. The problem here I think is this: there are too many requests sent by the Java side. I'll look for a way to reduce this; if we can do this in less than 3-5 trips (currently I'm seeing 12 trips for a simple button push, and some data seems to repeat), it should speed up things.

#1112 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811q Revision 10982

Really good!

#1113 Updated by Constantin Asofiei over 8 years ago

1811q rev 10983 contains (for 2. pressing maximize button makes client unresponsive (CA) in note 1087):
  1. don't send empty drawing ops list to JS (is a no-op)
  2. fixed a bug in computing the widgets details to be sent to the java side

With these two, the trips are reduced; I don't think there is much else we can do at this time - the remaining trips are from focus switching or normal drawing operations (TC.independentEventDrawingBracket and TC.eventDrawingBracket) when help is set, focus is switched, etc.

Greg, I'm delaying 3 and 4 from note 1087 until 2424c is in trunk and 1811q is merged: I think 3 (window resize: the cursor is not restored in Swing (CA)) is fixed as part of 2424c and also there might be other window-related changes/fixes in there. I'll take 5. specify mouse actions for remaining widgets and test (CA) next.

#1114 Updated by Sergey Ivanovskiy over 8 years ago

Greg, please review committed revision 10984. Drawing functionalities are moved to p2j.canvas_renderer.js in order to reuse drawing functionalities for the task bar.

#1115 Updated by Sergey Ivanovskiy over 8 years ago

Committed revision 10985. Added the first task bar version, iconify/deiconify don't work as expected. Planning to fix it. Committed revision 10986 is only to improve the code format.

#1116 Updated by Sergey Ivanovskiy over 8 years ago

Committed revision 10987. Fixed tooltips for task icons. Now if we click on the minimize button, the action sinks on the server side.
Greg, please help how to find the active window, it needs for the task bar to manage its state consistently?

#1117 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811q Revision 10983

Nice improvements.

#1118 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811q Revision 10987

Definitely there is a lot of good work here. I have checked in minor edits. Some thoughts:

1. I was not planning to support tooltips, nor drag/drop. We are already going to have a heavy load of mouse event processing and I am worried that adding all the overhead in p2j.virtual_desktop.js is too much.

2. Instead of tooltips, please just include the window title text in the taskbar "button".

3. The most important thing is to get the iconify/deinconify processing working properly.

4. Tooltips don't work for me. They are being removed anyway, but I thought you should know it is not fixed or working.

5. In p2j.virtual_desktop.js, search on // MISSING JAVADOC.

6. We can no longer access the "View Log" button because it is hidden behind the taskbar. Perhaps a special button on the taskbar could implement the same log viewing feature.

#1119 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

3. The most important thing is to get the iconify/deinconify processing working properly.

The base logic for iconify/deiconify is working now - some state was being set backwards and when window is deiconified (by the taskbar callback on mouse click) it needs to call window.deiconify too.

Beside this, I'm working on fixing an issue related to event priority: a mouse event which is handled by a high-level function at the JS code (like window iconify) needs to have precedence over the default callback in p2j.screen.js:raiseMouseEvent.

#1120 Updated by Sergey Ivanovskiy over 8 years ago

1. I was not planning to support tooltips, nor drag/drop. We are already going to have a heavy load of mouse event processing and I am worried that adding all the overhead in p2j.virtual_desktop.js is too much.

I beg let it be for a while unless it blocks the performance.

2. Instead of tooltips, please just include the window title text in the taskbar "button".

It needs to reuse "draw text on the canvas" functionalities from Window.

3. The most important thing is to get the iconify/deinconify processing working properly.

Working on this issue.

4. Tooltips don't work for me. They are being removed anyway, but I thought you should know it is not fixed or working.

Probably the tooltips don't work because the window title isn't set. I have added SET_TITLE log and it doesn't appear. Also the standalone test for p2j.virtual_desktop.js, p2j.stokes.js and p2j.canvas_renderer.js proves it should work as expected to simulate a button's tooltip.

6. We can no longer access the "View Log" button because it is hidden behind the taskbar. Perhaps a special button on the taskbar could implement the same log viewing feature.

Planning to add "View Log" button to the task bar.

#1121 Updated by Sergey Ivanovskiy over 8 years ago

3. The most important thing is to get the iconify/deinconify processing working properly.

The base logic for iconify/deiconify is working now - some state was being set backwards and when window is deiconified (by the taskbar callback on mouse click) it needs to call window.deiconify too.

Beside this, I'm working on fixing an issue related to event priority: a mouse event which is handled by a high-level function at the JS code (like window iconify) needs to have precedence over the default callback in p2j.screen.js:raiseMouseEvent.

Constantin, the iconify/deiconify will be working properly as soon as you fix the priority of mouse handlers. I also find that WindowIconify is not called but the default handler catch all events.

   /**
    * Iconify this window.
    */
   Window.prototype.iconify = function()
   {
      p2j.logger.log("Window.prototype.iconify(" + this.id + ")");

      this.setVisible(false); // window should be hidden

      taskBar.iconify(this.id);
   };

   /**
    * De-iconify this window.
    */
   Window.prototype.deiconify = function()
   {
      p2j.logger.log("Window.prototype.deiconify(" + this.id + ")");

      this.setVisible(true); // window should be visible

      taskBar.deiconify(this.id);
   };

#1122 Updated by Sergey Ivanovskiy over 8 years ago

Greg, Constantin, do you agree to move drawString and drawScaledString to p2j.canvas_renderer.js. I think currentFont also is going to be moved.

            case ops.DRAW_STRING:
..................................
               if (centered)
               {
                  // in this case, the "y" is the height in which it needs to be vertically centered
                  y = y / 2;

                  this.ctx.textBaseline = 'middle';
               }
               else
               {
                  this.ctx.textBaseline = 'bottom';
               }

               this.ctx.fillText(text, x, y);

               extra = " text = " + text + "; x = " + x + "; y = " + y +
                       "; centered = " + centered;
               break;
            case ops.DRAW_STRING_SCALED:
.................................................               
               var textWidth = p2j.fonts.getTextWidth(currentFont, text);
               var textHeight = p2j.fonts.getTextHeight(currentFont, text);

               var widthScale = lwidth / textWidth;
               var heightScale = lheight / textHeight;

               // scale drawing context to computed width
               // this scales the desired structure.x as well so it needs adjusting
               this.ctx.save();
               this.ctx.scale(widthScale, heightScale);

               var scaleBackWidth = 1 / widthScale;
               var scaleBackHeight = 1 / heightScale;

               if (centered)
               {
                  // in this case, the "y" is the height in which it needs to be vertically centered
                  y = y / 2;

                  this.ctx.textBaseline = 'middle';
               }
               else
               {
                  this.ctx.textBaseline = 'bottom';
               }

               this.ctx.fillText(text, x * scaleBackWidth, y * scaleBackHeight);
               this.ctx.restore();

               break;

#1123 Updated by Constantin Asofiei over 8 years ago

Sergey Ivanovskiy wrote:

Constantin, the iconify/deiconify will be working properly as soon as you fix the priority of mouse handlers. I also find that WindowIconify is not called but the default handler catch all events.

See 1811q revision 10990 - iconify should work now.

Greg, Constantin, do you agree to move drawString and drawScaledString to p2j.canvas_renderer.js.

Yes, these are OK.

I think currentFont also is going to be moved.

If you move currentFont, you need to move SET_FONT and SET_FONT_STYLE code too.

#1124 Updated by Sergey Ivanovskiy over 8 years ago

Great! iconify/deiconify works properly I have checked it. Committed revision 10991. - javadoc and set_title logs.

#1125 Updated by Sergey Ivanovskiy over 8 years ago

Committed revision 10992. Added drawText and drawScaledText to CanvasRenderer.

#1126 Updated by Sergey Ivanovskiy over 8 years ago

Committed revision 10993. Added view log button to the task bar and changed to use only one listener for all task icon widgets on the task bar. The following issue arises a title text for a window task widget can vary its size and if we permit a task widget to change its size, it will look ugly for my aesthetic feeling. For an example let us consider the Ubutu desktop, all window task icons have the same size.

#1127 Updated by Greg Shah over 8 years ago

The following issue arises a title text for a window task widget can vary its size and if we permit a task widget to change its size, it will look ugly for my aesthetic feeling. For an example let us consider the Ubutu desktop, all window task icons have the same size.

Agreed. Make the buttons the same size. Size them to fit the smaller of:

  • taskbar_width / number_of_windows
  • a width that can fit the entire text of the widest existing window title

Truncate the text as needed.

#1128 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811q Revision 10990

I'm fine with the changes.

#1129 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811q Revision 10992

I'm fine with the changes.

Please do not use /* or /** style comments inside code blocks. Per our coding standards, // should be used for code comments.

#1130 Updated by Sergey Ivanovskiy over 8 years ago

Committed revision 10994. Greg, please review "set title" changes.

#1131 Updated by Sergey Ivanovskiy over 8 years ago

Greg, Constantin, is it possible on the server (ThinClient) side to determine a widget component at the given point (x, y) in order to simulate Swing/AWT?

java.awt.Container container;
...................
java.awt.Component component;
component = container.findComponentAt(x,y);

If it is possible, it will simplify a mouse handling from the web client. Find a widget at (x,y) on the server side and transmit the mouse event to it. Also a mouse click event can be artificially generated after mouse down and mouse up occurred one after another during a click interval. I don't understand why we make the special usecases for web minimize, maximize and close mouse action if the real widgets are on the server and we only need to draw them on the canvas.

#1132 Updated by Sergey Ivanovskiy over 8 years ago

Greg, please review the committed revision 10995. All known task bar usecases are implemented here.

#1134 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811q Revision 10995

This is really good!

1. The unconditional call to gd.setTitle() in WindowTitleBar.draw() is called too much. This only needs to be called if the title has changed, right?

2. Using SET_FONT to also set the taskbar font is wrong. We are constantly changing the font via SET_FONT, but none of those changes should affect the taskbar. By doing this, the font is often changing and it doesn't always match the sizes calculated for the task button.

3. Please add a larger margin for the border of each task button. The text ends too close to the edge.

#1135 Updated by Sergey Ivanovskiy over 8 years ago

2. Using SET_FONT to also set the taskbar font is wrong. We are constantly changing the font via SET_FONT, but none of those changes should affect the taskbar. By doing this, the font is often changing and it doesn't always match the sizes calculated for the task button.

Going to use the font that is provided by p2j.init(cfg)

'font' : {'name' : '${font.name}', 'size' : ${font.size}, 'color' : {'f' : '${font.color}', 'b' : '${font.background}'}}

#1136 Updated by Greg Shah over 8 years ago

Rebased task branch 1811q from trunk revision 10933. Latest revision is now 10998.

#1137 Updated by Sergey Ivanovskiy over 8 years ago

Greg, please review the committed revision 10999. 1) Fixed gd.setTitle() on draw(), 2) Sets the default font for the task bar according to the provided configuration p2j.init(cfg) 3) Fixed title margins. The remove task icon use case is not implemented. I think if the window is closed the corresponding task icon should be removed from the task bar.

#1138 Updated by Constantin Asofiei over 8 years ago

Sergey Ivanovskiy wrote:

The remove task icon use case is not implemented. I think if the window is closed the corresponding task icon should be removed from the task bar.

As I recall, hiding a window in 4GL removes it from the taskbar - so the taskbar icon should be in sync with window's visibility (and also removed when the window gets destroyed).

#1139 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811q Revision 10999

The result is good.

1. The two new members of WindowTitleBar.WindowTitle need javadoc.

2. In p2j.virtual_desktop.js, this code:

     var DRAGGED_OPACITY = 0.3;
      /**
       * Top padding from the task bar border
       */
      var TOP_PADDING    = 2;
      /**
       * Bottom padding from the task bar border
       */
      var BOTTOM_PADDING = 2;
      /**
       * Left padding from the task bar border
       */
      var LEFT_PADDING  = 2;
      /**
       * Right padding from the task bar border
       */
      var RIGHT_PADDING = 2;

should look like this:

      var DRAGGED_OPACITY = 0.3;

      /** Top padding from the task bar border */
      var TOP_PADDING    = 2;

      /** Bottom padding from the task bar border */
      var BOTTOM_PADDING = 2;

      /** Left padding from the task bar border */
      var LEFT_PADDING  = 2;

      /** Right padding from the task bar border */
      var RIGHT_PADDING = 2;
  • single line javadoc should not be expanded into 3 lines
  • there must be a blank line between data members (and between methods or between data members and methods)

There are other areas of that same file that also need to be fixed up.

#1140 Updated by Greg Shah over 8 years ago

The remove task icon use case is not implemented. I think if the window is closed the corresponding task icon should be removed from the task bar.

Agreed.

As I recall, hiding a window in 4GL removes it from the taskbar - so the taskbar icon should be in sync with window's visibility (and also removed when the window gets destroyed).

Please confirm this on windev01. If correct, then duplicate this behavior too.

#1141 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

The remove task icon use case is not implemented. I think if the window is closed the corresponding task icon should be removed from the task bar.

Agreed.

A note here: pressing the window close button doesn't close it, it just raises a WINDOW-CLOSE event and the application logic decides what to do, via a trigger. So the javascript code doesn't have to perform anything special when the window close button is pressed.

#1142 Updated by Sergey Ivanovskiy over 8 years ago

Proved on windev01 that if a window is hidden, it is removed from the task bar. But Window 8 OS stacks all windows created by the progress. It is displayed on the attached screen shoots.

#1143 Updated by Constantin Asofiei over 8 years ago

Sergey Ivanovskiy wrote:

But Window 8 OS stacks all windows created by the progress. It is displayed on the attached screen shoots.

Isn't stacking just a setting at the Window's OS taskbar? I don't think we need to emulate this.

#1144 Updated by Greg Shah over 8 years ago

Isn't stacking just a setting at the Window's OS taskbar? I don't think we need to emulate this.

Agreed. Stacking is not needed. In fact it would just result in there only ever being a single icon on the task bar since in our system you cannot access more than one Progress client from the same web page (virtual desktop). It is probably something to resolve later, but for now each 4GL window should have its own task button.

#1145 Updated by Greg Shah over 8 years ago

Proved on windev01 that if a window is hidden, it is removed from the task bar.

This one does need to be implemented, otherwise we would allow the user to select a window that the 4GL programmer would never expect is active.

#1146 Updated by Greg Shah over 8 years ago

2. pressing maximize button makes client unresponsive (CA)

Although the client is no longer unresponsive, the maximized button remains in a "pressed" state. Is this something that is expected to be fixed with item 5 or is it something that needs separate attention?

#1147 Updated by Greg Shah over 8 years ago

Sergey: On the taskbar, please draw the background of the inactive window task buttons as a lighter grey color so that the active/inactive buttons can be easily distinguished.

#1148 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

2. pressing maximize button makes client unresponsive (CA)

Although the client is no longer unresponsive, the maximized button remains in a "pressed" state. Is this something that is expected to be fixed with item 5 or is it something that needs separate attention?

I'm working on this, the Caption button issue can be seen in Swing, too. I've fixed this now, but mouseClicked event still has problems with BUTTON and scroll-bar buttons. Draw is not being performed for some reason.

#1149 Updated by Sergey Ivanovskiy over 8 years ago

draw the background of the inactive window task buttons as a lighter grey color so that the active/inactive buttons can be easily distinguished.

Now if the task button is active, it has the background that is lighter than the background of the inactive. The lighter color is created by
lightenColor(color) of CanvasRenderer. In its active state the button text color is 10 times lighter than the inactive text color textColor and is calculated by 10 * lightenColor(textColour). Thus it needs to revert the background active and inactive colors.
Greg, please approve the following requirement for the task button.
Let backgroundActiveColor and textActiveColor be the background color and the text color for the active task bar button respectively.
Let backgroundInactiveColor and textInactiveColor be the background color and the text color for the inactive task bar button respectively.
Then,
backgroundInactiveColor = lightenColor(backgroundActiveColor) and
textInactiveColor=10 * lighthenColor(textActiveColor).

#1150 Updated by Greg Shah over 8 years ago

Please post an example screen shot of the results with your proposed inactive/active colors.

#1152 Updated by Sergey Ivanovskiy over 8 years ago

It can help to select the most usable colours.!TaskBarChangedColours.png! On the picture
the task bar background is [174, 174, 174] and active task icon colour is [144, 144, 144]. VirtualDesktop.html can be modified in order to find fitted colours

var desktop = new VirtualDesktop(null, [174, 174, 174], [144, 144, 144],
         p2j.fonts, p2j.logger);

#1153 Updated by Greg Shah over 8 years ago

OK, go with that approach.

I don't see the difference that the 10 * lighthenColor(textActiveColor) is making. But the overall result looks good.

#1154 Updated by Sergey Ivanovskiy over 8 years ago

Greg, please review the committed revision 11000. There are two cases to add new visible state to TaskIcon and to manage hidden elements by TaskBar. The second is selected because it saves the drawing code from modifications and the memory usage.

#1155 Updated by Constantin Asofiei over 8 years ago

Greg/Sergey, I have a large set of changes to commit to 1811q, related to mouse processing: please let me know if/when I can commit them.

#1156 Updated by Greg Shah over 8 years ago

please let me know if/when I can commit them.

Go ahead now.

#1157 Updated by Constantin Asofiei over 8 years ago

1811q revision 11001 contains:
  1. added mouse web actions for remaining widgets
  2. All windows listen to any mouse actions, at any time. Mouse actions can not be disabled during drawing, as on slower machines (or WEB driver) it might mean ignoring posted mouse released/clicked events, as P2J is busy drawing/processing the mouse pressed event - this fixes most of the drawing issues we were seeing in web/swing client. The only remaining case where mouse actions are not captured is when control is on server-side.
  3. first attempt at fixing the z-order
  4. fixed move to top/bottom/tab order at WindowGuiImpl - it must use the workspace content pane, otherwise these are no-ops, as the i.e. frame is not a direct child of the GUI window's content pane, but of the workspace.
  5. other misc issues in combo-box, editor, etc
Other issues:
  1. JS-side "editable widget" support is broken
  2. continue testing z-order
  3. add support for "anywhere action" on JS side (to allow to close the system menu on click outside of it).

Sergey, to answer your question in note 1131: I'm already doing something like this. On JS side, I'm registering mouse-sensitive areas (and the kind of mouse event they are sensitive to). If the mouse action is performed in such an area, then the event is sent to the Java side. See the p2j.screen:mouseAwareWidgets, osAwareWidgets and raiseMouseEvent(). On Java side, we have the AbstractContainer.findMouseSource, which determines the widget at some given coordinates - note that this may be overridden in sub-classes.

#1158 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811q Revision 11000

It looks very good.

The single line javadoc format is only used for data members. Functions like setDefaultTooltip(), draw(), setActive() and isActive() in p2j.virtual_desktop.js should retain the multiline javadoc format.

#1159 Updated by Greg Shah over 8 years ago

Sergey, I've switched "key processing differences for GUI" from Constantin to you. Please work on that next.

Constantin: would you please describe the process Sergey should use for this key processing work?

#1160 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811q Revision 11001

This is a really nice update.

My only question is whether we can disable mouse event processing when a given window is hidden/not visible. It seems like there will be overhead that can be avoided in such cases. Perhaps if we enable mouse events before we start the drawing of a window that is just made visible?

#1161 Updated by Sergey Ivanovskiy over 8 years ago

Committed revision 11002. Minor changes: fixed methods javadocs.

#1162 Updated by Sergey Ivanovskiy over 8 years ago

Greg, Constantin, how to go forward from the simple Hello program to more complicated? Tested an example taken from Progress Programming Handbook, but it failed with swing client also.

#1163 Updated by Greg Shah over 8 years ago

Try any of these:

./ask-gui.p
./hello.p
./demo/calc.p
./demo/calc-static.p
./demo/calc-static-chars.p
./demo/calc-static-pixels.p
./toggle_box/gui/tbx_present.p
./combo_box/combo_box9_1.p
./rectangle/rect_test2.p
./rectangle/rect_test6.p
./rectangle/rect_test7_1.p
./image/image0.p
./image/image10_1.p
./button/gui_btn_test3.p
./button/gui_btn_test4.p
./button/gui_btn_test5.p
./demo/movie-ratings-dynamic.p
./demo/movie-ratings-static.p
./window_sizing/create_empty_window.p
./window_sizing/default_empty_window.p
./window_sizing/test_runner.p
./simple_alert_box.p
./menu/simple_sm.p
./menu/popup_ext.p

But for now, you should start by looking carefully at the GUI-specific keyboard classes (start with GuiKeyboard).

Constantin will provide more details of the process.

#1164 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

My only question is whether we can disable mouse event processing when a given window is hidden/not visible. It seems like there will be overhead that can be avoided in such cases. Perhaps if we enable mouse events before we start the drawing of a window that is just made visible?

The mouse listeners are registered per each canvas, so if the canvas for a window in JS is not visible, it will not capture any mouse events. We don't have global mouse listeners.

#1165 Updated by Constantin Asofiei over 8 years ago

Sergey, for the keyboard support for the Web client, we need a JS version of what driver.swing.WinKeyboardReader class does in its keyPressed and keyTyped implementation. Basically, this ends up in a translation of the keys pressed in the JS browser (including the ctrl/alt/shift combinations) to key codes known by 4GL. For the Chui Web driver, there are the p2j.keyboard.js and p2j.keymap.js files, which are a JS version of what LinuxKeyboardReader does for the ChUI Swing driver.

A key table with key codes as reported by 4GL can be found in testcases/uast/win-gui.txt.

You may want to build a simple JS script and capture the JS key codes, to determine which keys have different codes than 4GL. This may help you build a the translation table (referred as xlate in some files). We need this because some keys (like ALT) have different codes in 4GL and Windows. The function keys may need some special cases, as some of them may be captured by the Browser. Same as some CTRL combinations like CTRL-S/O/P etc.

Details about how 4GL builds the key code can be found in the Programming Interfaces book (dvpin.pdf), the Key codes and key labels chapter on page 209.

#1166 Updated by Constantin Asofiei over 8 years ago

Sergey Ivanovskiy wrote:

Greg, Constantin, how to go forward from the simple Hello program to more complicated? Tested an example taken from Progress Programming Handbook, but it failed with swing client also.

I've added two new tests in testcases/uast/demo, simple_windows.p and demo_widgets.p. The first one creates two windows and the second one has a frame with all currently supported widgets which can interact with a mouse.

#1167 Updated by Greg Shah over 8 years ago

Rebased task branch 1811q from P2J trunk revision 10935. The latest branch revision is 11004.

Please save off and re-apply any pending changes before executing a bzr update.

#1168 Updated by Sergey Ivanovskiy over 8 years ago

After this update the web images rendering is broken. I need to fix it.

#1169 Updated by Sergey Ivanovskiy over 8 years ago

Committed revision 11005. Minor changes to truncate long task icon titles.

#1170 Updated by Greg Shah over 8 years ago

After this update the web images rendering is broken.

The parts that are broken are implemented using VectorGraphicsHelper to create images from SVG files. Those images are then drawn with drawImage(). This replaces manual/dynamic drawing code for button arrows and the min/max/close buttons. Somehow, the web client is not working right. The Swing client is working fine, so it is something that is not working with just the web GUI client.

I was going to demo the web GUI in a conference call at 10:00 (EDT)/15:00 (GMT). Can you fix it in time?

#1171 Updated by Greg Shah over 8 years ago

Interestingly, the combo-box button mostly works (see demo/demo_widgets.p):

The size seems slightly too big, but otherwise it is displaying. I think the way that the combo-box is doing its drawing of these SVG images must be different enough to make it work.

#1172 Updated by Sergey Ivanovskiy over 8 years ago

Committed revision 11007. Fixed the background artefacts. Now VirtualScreen is not like it is supposed to be. It doesn't save its state or screen.

#1173 Updated by Greg Shah over 8 years ago

The taskbar also does not switch active windows or track active windows correctly. See demo/simple_windows.p.

#1174 Updated by Sergey Ivanovskiy over 8 years ago

Some times if iconify one of these 2 - windows (demo/simple_windows.p) the other one is also iconified. I don't know how to define the active window. I posted that question to #1811-1116
1) If the application creates new window is it active
2) Then the application creates new child window is it becomes active. How to define the current focus if these two windows are not modal?
3) The application sets the window visibility to hidden the task button is disapper, and its active state also becomes inactive.
4) The application sets the window visible. Does it lead to this window is active?

#1175 Updated by Constantin Asofiei over 8 years ago

Sergey Ivanovskiy wrote:

Some times if iconify one of these 2 - windows (demo/simple_windows.p) the other one is also iconified. I don't know how to define the active window. I posted that question to #1811-1116
1) If the application creates new window is it active
2) Then the application creates new child window is it becomes active. How to define the current focus if these two windows are not modal?
3) The application sets the window visibility to hidden the task button is disapper, and its active state also becomes inactive.
4) The application sets the window visible. Does it lead to this window is active?

I don't think we need to worry about 4GL-specific logic at the JS driver for now. I think is enough to assume that whatever the top window is (as returned by p2j.screen.topWindowId), acts as the active window and must have the taskbar icon highlighted. The logic which decides which window is on top is on Java side, with a small part on p2j.mouse.js, which calsl moveToTop to activate a window when the canvas is clicked. You may need to integrate the taskbar logic in p2j.screen.moveToTop.

#1176 Updated by Sergey Ivanovskiy over 8 years ago

Agreed that the task bar is only to display what it is directed to do. The task bar doesn't minimize and restore the windows, it is only to highlight or not or remove the corresponding task buttons.

#1177 Updated by Sergey Ivanovskiy over 8 years ago

The taskbar also does not switch active windows or track active windows correctly. See demo/simple_windows.p.

Greg, does it need for the presentation demo/simple_windows.p ? I think the bug is not only due to the task bar implementation. I can write tests for all its usecases.

#1178 Updated by Greg Shah over 8 years ago

does it need for the presentation demo/simple_windows.p ?

No, I had other testcases that showed what I needed.

#1179 Updated by Sergey Ivanovskiy over 8 years ago

Going to implement this logic for the task bar
The only one button can be active:
If new button becomes active, the old active is deactivated.
The task bar will provide a new method to set active state for the task button

function setTaskIconActive(windowId, active)

Clicking on the button invokes the registered callback requests the server about restoring the window.
Constantin, you propose to set active state moveToTop? it is only used by function processWindowFocus in the p2j.mouse.js.
How to set initial state what event or method call should set it correctly?

#1180 Updated by Sergey Ivanovskiy over 8 years ago

This code rises an interesting situation it hides the window and sets it on the top of the others.

   /**
    * Show or hide the window.
    *                              
    * @param    {boolean} visible  
    *           Flag that determines the new visibility state.
    */
   Window.prototype.setVisible = function(visible)
   {
      // this code depends on each canvas being style.position = 'absolute' so that it is
      // outside of the browser's layout processing
      this.canvas.style.visibility = visible ? "visible" : "hidden";

      // bring to the top of the z-order
      moveZOrderEntryToTop(this.id);
   };

#1181 Updated by Constantin Asofiei over 8 years ago

Sergey Ivanovskiy wrote:

This code rises an interesting situation it hides the window and sets it on the top of the others.
[...]

I think the call moveZOrderEntryToTop is incorrect... add this trigger to simple_windows.p before the last UPDATE statement:

on "1" anywhere do:
   h:visible = not h:visible.
end.

and you will see that changing the visibility keeps the window in its place.

About your other question:
  1. there is the restackZOrderEntries - this again might change the top window, so this needs to update the taskbar icon, too
  2. moveToTop is one place which should be handled, but I think it should be renamed to "activate window"
  3. creating a window, although it moves it to top, it doesn't make it active. Currently P2J makes it active, it assumes whatever the top window is, is the ACTIVE-WINDOW. I think we still have some problems here.

#1182 Updated by Sergey Ivanovskiy over 8 years ago

Committed revision 11008. Added setTaskIconActive(windowId, active). For the tested example simple_windows.p if we switch between windows by the mouse the active buttons are also changed. But there is a bug if two windows are minimized and we select the second child window by clicking on its task button. At first its task button becomes active (correct), then after calling its "on click" callback the parent window task button becomes active and two windows are displayed.

#1183 Updated by Greg Shah over 8 years ago

Code Review Rask Branch 1811q Revision 11008

The setTaskIconVisible() seems to have duplicative code. It definitely seems like the updateTaskBar() is going to be called twice on the taskIcons in the visible true case. It is less clear in the visible false case. The code is definitely confusing. I'd like to see it made more clear. Fewer places that call updateTaskBar() would help.

#1184 Updated by Greg Shah over 8 years ago

But there is a bug if two windows are minimized and we select the second child window by clicking on its task button. At first its task button becomes active (correct), then after calling its "on click" callback the parent window task button becomes active and two windows are displayed.

In the 4GL these windows are definitely treated as separate (one is not the child of the other). This means they can be independently iconified/deiconified. Please do fix the issue.

#1185 Updated by Sergey Ivanovskiy over 8 years ago

Committed revision 11009. Fixed the initial task bar state.
Located the following bug. The sequence to reproduce.
simple_windows.p creates two window with id 1 and 21, respectively.
The initial state is 1 is active and GuiWebDriver references the GuiWebEmulatedWindow for the id = 1.
1) Minimize window having id = 1 by clicking on the corresponding caption button. The window 1 is minimized and the window 21 becomes active.
2) Minimize window 21. The window 21 is minimized. There are no any visible windows on the screen and and GuiWebDriver references the GuiWebEmulatedWindow for the id = 21.
3) Restore window 1. The result is two window are restored.
The bug is due to the fact that when GuiWebDriver gets the "restore window 1" request, it references the last visible window.
That is the following field has incorrect reference to window 21.

public abstract class AbstractGuiDriver<F>
extends AbstractDriver<GuiPrimitives, GuiOutputManager>
implements GuiDriver<F, BufferedImage>
{
.................................................
    /** Currently selected window emulator. */
   protected EmulatedWindowState ews = null;
 ...............................................
}

As a solution can we send WindowActivate in these cases before sending the restore window message?

#1186 Updated by Sergey Ivanovskiy over 8 years ago

Committed revision 11010 fixes comments.
Constantin, if we minimize/restore the selected window, we get the round trip for the iconify/deiconify action:
1) Click on the minimize button.
2) Then WindowIconify handler is invoked for the selected window win, it calls win.iconify() and then sends the minimize window request to to the server.
3) WebGuiDriver public void windowIconified(int windowId, boolean state) is invoked. It redirects to ews.iconifyWindow(sourceId); of GuiWebEmulatedWindow.
4) Then setIconificationState(true); of GuiWebEmulatedWindow is invoked. It sends the message "set iconification state" (SET_ICONIFICATION_STATE) to the client(!!!important!!! it initiates before Keyboard.SE_WINDOW_MINIMIZED is processed!!!). The client gets it and calls win.iconify() again. And after sending SET_ICONIFICATION_STATE it posts the target minimize event

      WindowGuiImpl window = (WindowGuiImpl) ThinClient.getInstance().getWidget(windowId);
      WindowEvent evt = new WindowEvent(window, Keyboard.SE_WINDOW_MINIMIZED);
      evt.setOrigin(sourceId);
      ThinClient.getInstance().postOSEvent(evt);

The same round trip happens with win.deiconify(). If it is the target behaviour we can check if the window is already minimized/restored inside the corresponding methods iconify/deiconify of Window.

#1187 Updated by Sergey Ivanovskiy over 8 years ago

Found an inconsistent state for simple_windows.p. From logs produced by this code (GuiWebDriver) in the case two windows are minimized, the flag empty screen is always false.

   public void windowIconified(int windowId, boolean state)
   {
      System.err.println("windowIconified(" + windowId + ", " + state + ")");
      List<TitledWindow<?>> windowsList = WindowManager.windowList();

      boolean emptyScreen = true;
      for(TitledWindow<?> win : windowsList)
      {
         System.err.println("windowId = " + win.getId().asInt() + " visible = " + win.isVisible());
         if (win.isVisible())
         {
            emptyScreen = false;
            break;
         }
      }
      System.err.println("current window = " + ((ews != null) ? ews.getWindowId(): "null"));
      if (emptyScreen)
      {
         System.err.println("empty screen for windowIconified(" + windowId + ", " + state + ")");
      }

      if (state)
      {
         iconifyWindow(windowId);
      }
      else
      {
         deiconifyWindow(windowId);
      }
   }

Greg, Constantin, what could it mean?

#1188 Updated by Sergey Ivanovskiy over 8 years ago

Propose to overide

   /* (non-Javadoc)
    * @see com.goldencode.p2j.ui.client.gui.driver.AbstractGuiDriver#iconifyWindow(int)
    */
   @Override
   public void iconifyWindow(int sourceId)
   {
      selectWindow(sourceId);
      super.iconifyWindow(sourceId);
   }

   /* (non-Javadoc)
    * @see com.goldencode.p2j.ui.client.gui.driver.AbstractGuiDriver#deiconifyWindow(int)
    */
   @Override
   public void deiconifyWindow(int sourceId)
   {
      selectWindow(sourceId);
      super.deiconifyWindow(sourceId);
   }


It seems to fix.

#1189 Updated by Sergey Ivanovskiy over 8 years ago

Greg, please review the committed revision 11011. It attempts to fix the incorrect current window bug.

#1190 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811q Revision 11011

1. In regard to adding the selectWindow() to the GuiWebDriver.iconifyWindow() and GuiWebDriver.deiconifyWindow(), I am wondering if the need for it is because in the Swing client we automatically get a window activation first. I'm generally OK with your approach here but I do worry that if we are really missing some activation events, then we will see other cases like this and it would be better to resolve the activation event problem root cause.

2. I see that in p2j.screen.js you have modified Window.prototype.setVisible() such that moveZOrderEntryToTop() is only called if visible == true. That intuitively seems correct, but the original specification for setVisible() was to always do it.

Constantin: what do you think about these two issues?

#1191 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

Code Review Task Branch 1811q Revision 11011

1. In regard to adding the selectWindow() to the GuiWebDriver.iconifyWindow() and GuiWebDriver.deiconifyWindow(), I am wondering if the need for it is because in the Swing client we automatically get a window activation first. I'm generally OK with your approach here but I do worry that if we are really missing some activation events, then we will see other cases like this and it would be better to resolve the activation event problem root cause.

The other callpath for each of the (de)iconify APIs (in WindowGuiImpl.iconify() and WindowGuiImpl.restore()) already call selectWindow() before calling the driver APIs. The problem is only for the web driver callpaths - both in GuiWebDriver.windowIconified() - this callpath was not calling selectWindow(). So, Sergey, please remove the iconify/deiconify APIs from GuiWebDriver and move the selectWindow() calls at AbstractGuiDriver.(de)iconifyWindow() APIs.

2. I see that in p2j.screen.js you have modified Window.prototype.setVisible() such that moveZOrderEntryToTop() is only called if visible == true. That intuitively seems correct, but the original specification for setVisible() was to always do it.

Greg, see the notes in 1181 - P2J is not tracking correctly the ACTIVE-WINDOW (I think) which should be on top. We currently move to top whichever window was made visible last, so the change in setVisible() just brings the JS driver in sync with the Swing driver.

Sergey, are the issues in notes 1185 and 1186 still an issue?

#1192 Updated by Sergey Ivanovskiy over 8 years ago

Committed revision 11012. Changed to improve visual effects of adding new windows.

1186 deserves to be clarified, because on my intuitive level it is incorrect if the underlined system don't minimize the selected window, but the web server part already minimizes the selected window before Keyboard.SE_WINDOW_MINIMIZED is processed by the sever side. Does my view miss key issues or is it incorrect?

The problem is only for the web driver callpaths - both in GuiWebDriver.windowIconified() - this callpath was not calling selectWindow(). .... remove the iconify/deiconify APIs from GuiWebDriver and move the selectWindow() calls at AbstractGuiDriver.(de)iconifyWindow() APIs.

If we change AbstractGuiDriver, then it influences the behaviour of Swing client, is it ok?

#1193 Updated by Constantin Asofiei over 8 years ago

Sergey Ivanovskiy wrote:

Committed revision 11012. Changed to improve visual effects of adding new windows.

1186 deserves to be clarified, because on my intuitive level if the underlined system don't minimize the selected window, but the web server part already minimizes the selected window before Keyboard.SE_WINDOW_MINIMIZED is processed by the sever side. Does my view miss key issues or is it incorrect?

From the documentation, all window-level state change events (WINDOW-MINIMIZED, WINDOW-MAXIMIZED, WINDOW-CLOSED, etc), Trigger executes after event takes
place.
So the 4GL event (e.g. raised via Keyboard.SE_WINDOW_MINIMIZED) needs to be raised AFTER the OS minimizes the window. So, your view is correct: if the OS-level window minimize event has been triggered, once it has finished, it needs to raise the WINDOW-MINIMIZE event in 4GL. But I don't understand this: if the underlined system don't minimize the selected window - how can this happen? AFAIK OS event can't be "rolled back" in 4GL: from docs, However, since the native system has control, a NO-APPLY does not stop the event from occurring., so once the OS has triggered the event, it can't be stopped.

The problem is only for the web driver callpaths - both in GuiWebDriver.windowIconified() - this callpath was not calling selectWindow(). .... remove the iconify/deiconify APIs from GuiWebDriver and move the selectWindow() calls at AbstractGuiDriver.(de)iconifyWindow() APIs.

If we change AbstractGuiDriver, then it influences the behaviour of Swing client, is it ok?

Yes, the Swing client was already selecting the window, so nothing is changing there.

#1194 Updated by Sergey Ivanovskiy over 8 years ago

Committed revision 11013. Changed AbstractGuiDriver.

if the OS-level window minimize event has been triggered, once it has finished, it needs to raise the WINDOW-MINIMIZE event in 4GL.

Thank you, it clarifies the case of web OS-level window minimize event is handled by MouseIconify and GuiWebDriver directly.

#1195 Updated by Sergey Ivanovskiy over 8 years ago

Committed revision 11014 makes a window can be focused.

#1196 Updated by Sergey Ivanovskiy over 8 years ago

http://unixpapa.com/js/key.html
https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode
describe the well-known issue that key codes linked to key down,key pressed and key up events are not the same between browsers.
We have p2j.keyboard.js that registers the global listeners for "keydown", "keypress" and "keyup". These listeners translate js generated key events to 4GL events and should work properly for CHUI. Is it true that GUI only adds new key events and actions linked to them?
I think it would be good to separate browsers dependent issues from 4GL keys mapping by using key symbols.

#1197 Updated by Sergey Ivanovskiy over 8 years ago

Sergey, for the keyboard support for the Web client, we need a JS version of what driver.swing.WinKeyboardReader class does in its keyPressed and keyTyped implementation. Basically, this ends up in a translation of the keys pressed in the JS browser (including the ctrl/alt/shift combinations) to key codes known by 4GL. For the Chui Web driver, there are the p2j.keyboard.js and p2j.keymap.js files, which are a JS version of what LinuxKeyboardReader does for the ChUI Swing driver.

A key table with key codes as reported by 4GL can be found in testcases/uast/win-gui.txt.

You may want to build a simple JS script and capture the JS key codes, to determine which keys have different codes than 4GL. This may help you build a the translation table (referred as xlate in some files). We need this because some keys (like ALT) have different codes in 4GL and Windows. The function keys may need some special cases, as some of them may be captured by the Browser. Same as some CTRL combinations like CTRL-S/O/P etc.

Details about how 4GL builds the key code can be found in the Programming Interfaces book (dvpin.pdf), the Key codes and key labels chapter on page 209.

I have tested uast/demo/demo_widgets.p comparing how the keyboard are supported by the web and swing clients. The TAB key and SHIFT+TAB works similar for these clients. On the Swing GUI and Web GUI we can't move across frames, thus the focus traversal works only if a frame is selected by the mouse pointer. In that case the focus is moved in the bounds of the selected frame. The selection keys SHIFT + LEFT_ARROW and CTRL+C doesn't work for Web GUI (the clipboard issue), there are browser default actions that can be prevented. For example we can suppress the default browser action if one from the modifiers keys: ALT, SHIFT and CTRL, is pressed.
Constantin, what do you think if we leave the p2j.keyboard.js and p2j.keymap.js as they are and implement only suppressing the default browser action on ALT, SHIFT and CTRL. Are there missed issues?

#1198 Updated by Greg Shah over 8 years ago

At a minimum, the ESC key is processed differently. In GUI it is passed for processing while in ChUI the ESC key is a kind of modifier.

#1199 Updated by Constantin Asofiei over 8 years ago

Sergey Ivanovskiy wrote:

Constantin, what do you think if we leave the p2j.keyboard.js and p2j.keymap.js as they are and implement only suppressing the default browser action on ALT, SHIFT and CTRL. Are there missed issues?

The GUI keys are completely different than the ChUI keys. The function keys, ESC, CTRL for text navigation (ctrl-arrows, ctrl-a, ctrl-del, ctrl-shift- combinations), page-up/down, alt modifier combinations, etc, all are specially processed for GUI. Look into the key tables in testcases/uast/keyboard/ and see how different they are. We need separate versions for p2j.keyboard.js and p2j.keymap.js for the GUI client. Is not just a matter of capturing the key, we need to translate the key into a code which has meaning for 4GL.

Also, look into the SwingKeyboardReader and WinKeyboardReader - although there are some common portions with LinuxKeyboardReader, the key codes for the CTRL,SHIFT, and others are completely different.

#1200 Updated by Sergey Ivanovskiy over 8 years ago

Greg, Constantin, thank you for help it is like I threw the baby out with the bathwater. Planning to implement the functionality of WinKeyboardReader and to transform p2j.keyboard.js to be used with GUI and CHUI.

#1201 Updated by Sergey Ivanovskiy over 8 years ago

Constantin, WinKeyboardReader uses Keyboard class and there are hierarchies GuiKeyboard derived from ChuiWindowsKeyboard and ChuiWindowsKeyboard and ChuiLinuxKeyboard extends Keyboard. Thus, we need to implement these classes for JS client. Is there another solution?
At first I think that we can send key down, key pressed and key up events to the java side, parse them on the server side and reuse functionalities of WinKeyboardReader. But the implementations of p2j.keyboard.js and p2j.keymap.js confuse me to go this way. Could you help what the best solution would be? (The reimplement all these classes on jscript or to reuse the java implementation and do all key processing on the java server side.)

#1202 Updated by Greg Shah over 8 years ago

The p2j.keyboard.js and p2j.keymap.js where written specifically for the ChUI web client. I believe the idea was to map the browser key events into events that match how the NCURSES keys are generated. These are then forwarded to the Java side where our ChUI classes work without any changes.

If I understand correctly, I think this is why Constantin recommended that you write GUI-specific versions of p2j.keyboard.js and p2j.keymap.js. The idea would be to map the browser events into Windows/Swing key events and forward these compatible events to the Java side so that the Java keyboard classes for GUI can operate normally. All that forwarding and the Java side is already in place. If we just generate the key events with the right data and make sure that we forward the right events (and don't forward events that don't get generated under Windows/Swing), then it will "just work".

Constantin: did I mis-state anything?

#1203 Updated by Sergey Ivanovskiy over 8 years ago

Greg, I don't understand it because WebClientProtocol.readKey() gets a prepared key event from the chui web client that is only changed if web chui client uses virtual VT100 terminal mode.

#1204 Updated by Sergey Ivanovskiy over 8 years ago

WinKeyboardReader uses Keyboard class in this method private int processShift(int code). It follows that the js client needs to implement the same functionalities, doesn't it?

#1205 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

The p2j.keyboard.js and p2j.keymap.js where written specifically for the ChUI web client. I believe the idea was to map the browser key events into events that match how the NCURSES keys are generated. These are then forwarded to the Java side where our ChUI classes work without any changes.

If I understand correctly, I think this is why Constantin recommended that you write GUI-specific versions of p2j.keyboard.js and p2j.keymap.js. The idea would be to map the browser events into Windows/Swing key events and forward these compatible events to the Java side so that the Java keyboard classes for GUI can operate normally. All that forwarding and the Java side is already in place. If we just generate the key events with the right data and make sure that we forward the right events (and don't forward events that don't get generated under Windows/Swing), then it will "just work".

Constantin: did I mis-state anything?

Is mostly accurate; when the key is read by the Web driver, it gets passed directly to ThinClient.readKey - so the Java-side keyboard-related classes which work with i.e. Swing (i.e. WinKeyboardReader) are bypassed completely. The current JS key-related scripts are a duplicate of what the NCURSES/LinuxKeyboardReader do. We need to duplicate in JS the GUI classes.

#1206 Updated by Sergey Ivanovskiy over 8 years ago

Greg, Constantin, please approve that JS client should repeat functionalities of WinKeyboardReader and Keyboard classes.

#1207 Updated by Constantin Asofiei over 8 years ago

Sergey Ivanovskiy wrote:

Greg, Constantin, please approve that JS client should repeat functionalities of WinKeyboardReader and Keyboard classes.

You don't need to re-implement the Keyboard hierarchy in JS. Only the WinKeyboardReader code needs to be duplicated, as we can't send each key to the java side and process it there.

If you need access to Keyboard APIs from Javascript, you should do this:
  1. use a map to keep on JS side all the (code, label) pairs.
  2. if the label is not in this map, call the Java side via the web-socket message, to give you the label of a certain code, and store it in the cache.

Also, look into the SwingKeyboardReader constructor: that translation table needs to be duplicated.

#1208 Updated by Constantin Asofiei over 8 years ago

And something else: duplicating the WinKeyboardReader code is not so well phrased. The code needs to be adjusted so that the key codes captured by the browser (which may differ than what Swing is capturing) are translated to 4GL equivalent key codes.

#1209 Updated by Sergey Ivanovskiy over 8 years ago

And something else: duplicating the WinKeyboardReader code is not so well phrased. The code needs to be adjusted so that the key codes captured by the browser (which may differ than what Swing is capturing) are translated to 4GL equivalent key codes.

Okay, it will be taken into account. Thus from two alternative to implement similar functionalities and to reuse the java implementation and do all key processing on the java server side we prefer the first solution.
The second solution is complicated but practical. (There is the method developed by noVNC js library to map browsers generated key codes to the standard X11 keyboard encoding. The last one can be used to generate java.awt.KeyEvent.)

#1210 Updated by Greg Shah over 8 years ago

Hynek is going to rebase 1811q and then merge 2424d into 1811q. Before he does that, I wanted to give people a chance to check in any pending work.

Constantin/Sergey: please check in now or notify us that you don't need to check in.

My changes are already checked in, so I'm ready to go.

#1211 Updated by Hynek Cihlar over 8 years ago

Greg Shah wrote:

Hynek is going to rebase 1811q and then merge 2424d into 1811q. Before he does that, I wanted to give people a chance to check in any pending work.

Constantin/Sergey: please check in now or notify us that you don't need to check in.

Constantin, let me know when you ready.

#1212 Updated by Constantin Asofiei over 8 years ago

Hynek Cihlar wrote:

Greg Shah wrote:

Hynek is going to rebase 1811q and then merge 2424d into 1811q. Before he does that, I wanted to give people a chance to check in any pending work.

Constantin/Sergey: please check in now or notify us that you don't need to check in.

Constantin, let me know when you ready.

My changes are in rev 11018. Note that there are some drawing artifacts related to window background not being drawn fully (I guess some viewport/scrollpane dimension is not correct when the PaintEvent is posted). I'll fix after the rebase.

#1213 Updated by Hynek Cihlar over 8 years ago

1811q rebase is in progress, I will let you know when finished.

#1214 Updated by Hynek Cihlar over 8 years ago

Btw., do you also get that many false merge conflicts when rebasing? For example:

<<<<<<< TREE
** 013 HC  20150831 Improved GUI window resize performance.
** 014 CA  20150821 Added primitives related to mouse support.
=======
>>>>>>> MERGE-SOURCE

#1215 Updated by Hynek Cihlar over 8 years ago

Constantin, we both added NativeRectangle.toString() and Rectangle.toString(). I kept my version which also includes the simple class name and field names. Let me know if you have objections.

#1216 Updated by Greg Shah over 8 years ago

do you also get that many false merge conflicts when rebasing

Not usually, but sometimes deep into the rebasing process I do see things like this. It only seems to happen for me when it is a file that has had conflicts over multiple rebasing runs. This would happen when we have many commits in the branch and have had to rebase multiple times from different trunk revisions.

#1217 Updated by Hynek Cihlar over 8 years ago

Rebased task branch 1811q from P2J trunk revision 10937 (branch revision 11020). Also merged in changes from 2424d at revision 11021.

#1218 Updated by Sergey Ivanovskiy over 8 years ago

Running uast/demo/demo_widgets.p on the current version is failed for Swing and Web clients while it initializes the target menu.

#1219 Updated by Hynek Cihlar over 8 years ago

Sergey Ivanovskiy wrote:

Running uast/demo/demo_widgets.p on the current version is failed for Swing and Web clients while it initializes the target menu.

Looks like the menu should be attached to the widget tree before initializing. I think Constantin will know more.

#1220 Updated by Greg Shah over 8 years ago

This same abend occurs for all GUI testcases.

Looks like the menu should be attached to the widget tree before initializing.

Certainly true. The question is: what is different after the merge with 2424c and 2424d. Before those changes were integrated, there was no ordering problem.

#1221 Updated by Sergey Ivanovskiy over 8 years ago

There is another issue with SET_WINDOW_BOUNDS drawing primitive. The last one breaks the order of drawing operations for the web client. If the attached diff is applied, then the translate out of order errors appear.

Mismatching translate pop values, expected (0, 0) and found -4, -519). p2j.logger.js:83:7
me.error() p2j.logger.js:83
CanvasRenderer.prototype.untranslate() p2j.canvas_renderer.js:1096
Window.prototype.draw() p2j.screen.js:827
me.drawRectangles() p2j.screen.js:1866
me.init/ws.onmessage()

#1222 Updated by Greg Shah over 8 years ago

The WindowGuiImpl.createPopup() will cause the parent to be set on the MenuGuiImpl instance at the end of the method when it calls this.contentPane.add(menu);. The contentPane is a BorderPanel and comes from the Window class (parent of WindowGuiImpl). During the add() call, it sets the parent to this. The MenuGuiImpl.initialize() is called right after the construction of the MenuGuiImpl instance and definitely before the call to add().

Unfortunately, I don't have a copy of the 1811q branch before it was rebased. So I can't compare it with the original implementation by Constantin.

#1223 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

The WindowGuiImpl.createPopup() will cause the parent to be set on the MenuGuiImpl instance at the end of the method when it calls this.contentPane.add(menu);. The contentPane is a BorderPanel and comes from the Window class (parent of WindowGuiImpl). During the add() call, it sets the parent to this. The MenuGuiImpl.initialize() is called right after the construction of the MenuGuiImpl instance and definitely before the call to add().

Unfortunately, I don't have a copy of the 1811q branch before it was rebased. So I can't compare it with the original implementation by Constantin.

Something has changed on how the menu knows its parent. There are frameId and ownerId, I'm adjusting the code now to work OK. I'll have a fix shortly.

#1224 Updated by Constantin Asofiei over 8 years ago

1811q rev 11022 fixes the NPE in system popup menu initialization.

But all frames are now forced to be scrollable... Hynek, any idea why? Use testcases/uast/demo/demo_widgets.p as an example.

#1225 Updated by Greg Shah over 8 years ago

hello.p P2J Swing result as of 1811q rev 11022:

vs 4GL:

#1226 Updated by Greg Shah over 8 years ago

On the positive side: the scrollbar for the message area is no longer displaying when it should not. But the message area and main window content pane are both drawing incorrectly.

#1227 Updated by Sergey Ivanovskiy over 8 years ago

Please look at the committed revision 11023. Decided to fix the drawing ops for the web. But it doesn't fix "out of order translate" issue.

#1228 Updated by Hynek Cihlar over 8 years ago

Constantin Asofiei wrote:

1811q rev 11022 fixes the NPE in system popup menu initialization.

But all frames are now forced to be scrollable... Hynek, any idea why? Use testcases/uast/demo/demo_widgets.p as an example.

The default value for FRAME/DIALOG-BOX SCROLLABLE attribute should be true. I am just trying the demo_widgets.p what's broken.

#1229 Updated by Hynek Cihlar over 8 years ago

Greg Shah wrote:

hello.p P2J Swing result as of 1811q rev 11022:

vs 4GL:

It looks like the background color for message area is not properly resolved.

#1230 Updated by Constantin Asofiei over 8 years ago

Hynek Cihlar wrote:

Greg Shah wrote:

hello.p P2J Swing result as of 1811q rev 11022:

vs 4GL:

It looks like the background color for message area is not properly resolved.

I think is more of a PaintEvent problem: if a PaintEvent is posted only for the Hello World! line, then the second line is never drawn.

#1231 Updated by Constantin Asofiei over 8 years ago

Rev 11024 fixes the workspace background drawing.

The message area has two problems:
  1. for note 1229, in ScrollableListGuiImpl.draw:226, it clears the content but it doesn't set the color to anything, it inherits the previous color:
                gd.fillRect(nativeInsets.left,
                            nativeInsets.top,
                            d.width - nativeInsets.right,
                            d.height - nativeInsets.bottom);
    

    Commenting this code solves the problem, but I'm not sure if it breaks anything else
  2. a message area with no messages has incorrect width

And on a different note: the problems I'm seeing with the scrollbars are related to this: when they are in AS_NEEDED mode, the viewport needs to compute its dimension based if the scrollbars are needed or not. Also, the scrollbars need to check first if the content height/width can fit in the viewport height/width WITHOUT the scrollbars (thus the viewport uses the space which would have been occupied by the scrollbars). If it does not fit, then the scrollbars are "activated". The code which I think is at fault is in ScrollPaneGuiImpl.adjustScrollbars lines 445 and 451.

#1232 Updated by Hynek Cihlar over 8 years ago

Constantin Asofiei wrote:

And on a different note: the problems I'm seeing with the scrollbars are related to this: when they are in AS_NEEDED mode, the viewport needs to compute its dimension based if the scrollbars are needed or not. Also, the scrollbars need to check first if the content height/width can fit in the viewport height/width WITHOUT the scrollbars (thus the viewport uses the space which would have been occupied by the scrollbars). If it does not fit, then the scrollbars are "activated". The code which I think is at fault is in ScrollPaneGuiImpl.adjustScrollbars lines 445 and 451.

Unless you already did, let me fix this.

#1233 Updated by Constantin Asofiei over 8 years ago

Hynek Cihlar wrote:

Constantin Asofiei wrote:

And on a different note: the problems I'm seeing with the scrollbars are related to this: when they are in AS_NEEDED mode, the viewport needs to compute its dimension based if the scrollbars are needed or not. Also, the scrollbars need to check first if the content height/width can fit in the viewport height/width WITHOUT the scrollbars (thus the viewport uses the space which would have been occupied by the scrollbars). If it does not fit, then the scrollbars are "activated". The code which I think is at fault is in ScrollPaneGuiImpl.adjustScrollbars lines 445 and 451.

Unless you already did, let me fix this.

Yes, you can take it.

#1234 Updated by Hynek Cihlar over 8 years ago

Checked in revision 11026.

Improved scroll layout calculation for the case of native scrolling.
Fixed the text color of the frame active title.

#1235 Updated by Hynek Cihlar over 8 years ago

Constantin Asofiei wrote:

And on a different note: the problems I'm seeing with the scrollbars are related to this: when they are in AS_NEEDED mode, the viewport needs to compute its dimension based if the scrollbars are needed or not. Also, the scrollbars need to check first if the content height/width can fit in the viewport height/width WITHOUT the scrollbars (thus the viewport uses the space which would have been occupied by the scrollbars). If it does not fit, then the scrollbars are "activated". The code which I think is at fault is in ScrollPaneGuiImpl.adjustScrollbars lines 445 and 451.

This is resolved in revision 11026.

#1236 Updated by Sergey Ivanovskiy over 8 years ago

The attached zip contains all proposed changes and testKey.html from unixpapa that is used to test the mapping from the browser keys to 4GL keys (added Progress 4GL checkbox). These changes are not thoroughly tested and are not final. The second condition !lbl.endsWith("-" + lblOrig) of the following method int processShift(int code) of WinKeyboardReader is undocumented and unclear

   /**
    * Process the given key by setting the 9th bit.
    *
    * @param    code
    *           The key code to be processed.
    *           
    * @return   The processed key code or -1 if the SHIFT-key combination has no label.
*/
private int processShift(int code) {
..................................................................................
// modify the next character (in GUI 4GL, SHIFT is a modifier key)
code = code | 1 << 9;
String lbl = Keyboard.keyLabel(code);
if (lbl.length() == 0 || !lbl.endsWith("-" + lblOrig)) {
// return original code if there is no label or if the label doesn't end with the original key
return orig;
}
return code;
}

Greg, Constantin could you explain it. Believe it can help to implement the similar logic without calculating the actual labels.

#1237 Updated by Hynek Cihlar over 8 years ago

I have a new problem with the window resize cursor not being reverted back to its default. I didn't notice this earlier until the whole window workspace was made mouse sensitive.

The problem is with the way mouse events are processed. Currently only the top-most widget will receive the mouse event and so when the mouse cursor leaves the window border towards a different sensitive widget, different mouse handler is notified and the mouse cursor is not restored.

I think there are two possible solutions. (1) Make the mouse handlers (MouseResizeable class) somehow aware of the other instances and let them talk to each other to process this widget transition, or (2) introduce a propagation of events to multiple mouse handlers. Not only the top-most widget/handler would get chance to handle the event but also the (specially marked) widgets/handlers below in the z-order

#1238 Updated by Hynek Cihlar over 8 years ago

Hynek Cihlar wrote:

I have a new problem with the window resize cursor not being reverted back to its default. I didn't notice this earlier until the whole window workspace was made mouse sensitive.

The problem is with the way mouse events are processed. Currently only the top-most widget will receive the mouse event and so when the mouse cursor leaves the window border towards a different sensitive widget, different mouse handler is notified and the mouse cursor is not restored.

I think there are two possible solutions. (1) Make the mouse handlers (MouseResizeable class) somehow aware of the other instances and let them talk to each other to process this widget transition, or (2) introduce a propagation of events to multiple mouse handlers. Not only the top-most widget/handler would get chance to handle the event but also the (specially marked) widgets/handlers below in the z-order

Constantin, any thoughts on this?

#1239 Updated by Constantin Asofiei over 8 years ago

Hynek Cihlar wrote:

Hynek Cihlar wrote:

I have a new problem with the window resize cursor not being reverted back to its default. I didn't notice this earlier until the whole window workspace was made mouse sensitive.

The problem is with the way mouse events are processed. Currently only the top-most widget will receive the mouse event and so when the mouse cursor leaves the window border towards a different sensitive widget, different mouse handler is notified and the mouse cursor is not restored.

I think there are two possible solutions. (1) Make the mouse handlers (MouseResizeable class) somehow aware of the other instances and let them talk to each other to process this widget transition, or (2) introduce a propagation of events to multiple mouse handlers. Not only the top-most widget/handler would get chance to handle the event but also the (specially marked) widgets/handlers below in the z-order

Constantin, any thoughts on this?

This should have already been covered by SwingMouseHandler.mouseMoved... I'm looking into it.

Beside this, there is another issue: a MOUSE_RELEASED/CLICKED needs to use the same source as the initial MOUSE_PRESSED event (I have some changes related to this).

#1240 Updated by Sergey Ivanovskiy over 8 years ago

The attached zip contains all proposed changes and testKey.html from unixpapa that is used to test the mapping from the browser keys to 4GL keys (added Progress 4GL checkbox). These changes are not thoroughly tested and are not final. The second condition !lbl.endsWith("-" + lblOrig) of the following method WinKeyboardReader.processShift(int code) is undocumented and unclear
[...]
Greg, Constantin could you explain it. Believe it can help to implement the similar logic without calculating the actual labels.

The attached file contains all exception codes from 0 to 4096 that don't satisfy the second condition.

#1241 Updated by Constantin Asofiei over 8 years ago

Greg/Sergey, there is an issue with the stroke width which causes the web client to fail; in p2j.strokes.js:386

   function WidenPathRenderer(ctx, basicStroke, strokeColor)
   {
      var width = basicStroke.getWidth();
      var image = ctx.createImageData(width, width); /* 386 */
      var imageData = image.data;

tries to build an image with an width set to 0. This is coming from a primitive like this (I've changed to print it as float not integer):
PaintPrimitives.SET_LINE_STROKE  strokeStyleId = 1; strokeWidth = 0.20

The problem here is : in GuiWebEmulatedWindow, the PaintStructure.strokeWidth gets rounded to an integer before writing it into the message. And 0.20 is rounded to 0, which is passed to the JS side. More, the Swing driver is using the unrounded value (0.20), not the rounded value. So you see the difference in implementation.

My questions are:
  1. what is the correct value JS should receive?
  2. is the width supposed to always be an integer?
  3. and if it must be an integer, should we assume 1 for any value less than 1?

#1242 Updated by Sergey Ivanovskiy over 8 years ago

Constantin Asofiei wrote:

Greg/Sergey, there is an issue with the stroke width which causes the web client to fail; in p2j.strokes.js:386
[...]
tries to build an image with an width set to 0. This is coming from a primitive like this (I've changed to print it as float not integer):
[...]
The problem here is : in GuiWebEmulatedWindow, the PaintStructure.strokeWidth gets rounded to an integer before writing it into the message. And 0.20 is rounded to 0, which is passed to the JS side. More, the Swing driver is using the unrounded value (0.20), not the rounded value. So you see the difference in implementation.

My questions are:
  1. what is the correct value JS should receive?
  2. is the width supposed to always be an integer?
  3. and if it must be an integer, should we assume 1 for any value less than 1?

Yes, the width value should be a positive integer.

#1243 Updated by Constantin Asofiei over 8 years ago

Sergey Ivanovskiy wrote:

The attached zip contains all proposed changes and testKey.html from unixpapa that is used to test the mapping from the browser keys to 4GL keys (added Progress 4GL checkbox). These changes are not thoroughly tested and are not final. The second condition !lbl.endsWith("-" + lblOrig) of the following method WinKeyboardReader.processShift(int code) is undocumented and unclear
[...]
Greg, Constantin could you explain it. Believe it can help to implement the similar logic without calculating the actual labels.

The code in processShift tries to solve cases when the user presses for example CTRL-SHIFT-4, but there is no key with this label in 4GL. At the time when I coded this I was thinking that even if SHIFT is pressed, the key code returned must be CTRL-4, not CTRL-SHIFT-4, as there is no key with this label. But adding a ANY-KEY ANYWHERE trigger and pressing CTRL-SHIFT-4 produces the code 2612, which is key 4 with the CTRL and SHIFT modifiers set. So there might be a problem in WinKeyboardReader.processShift for this case.

But in the other case (where the suffix is checked) this is needed because pressing CTRL first, followed by SHIFT and a letter (for example a), it raises a key code of 1 in 4GL, corresponding for CTRL-A - so the SHIFT modifier is ignored. The rule in processShift can also be interpreted as "if, the labels computed from the key with the CTRL modifier and with the CTRL-SHIFT modifier are not the same after stripping the CTRL and CTRL-SHIFT from the label, then the SHIFT key is ignored". Although it doesn't work in all cases, as there are exceptions to this rule.

The attached file contains all exception codes from 0 to 4096 that don't satisfy the second condition.

Is this for the Swing client or the JS client?

#1244 Updated by Hynek Cihlar over 8 years ago

I checked in a fix for issue mentioned in #2252, note 1207. While bordered panel (BorderedPanelGuiImpl) translates draw location by the border insets it doesn't report the same amount of insets in its screen location. Checked in to 1811q, revision 11028. Constantin, please review.

#1245 Updated by Constantin Asofiei over 8 years ago

Hynek Cihlar wrote:

I checked in a fix for issue mentioned in #2252, note 1207. While bordered panel (BorderedPanelGuiImpl) translates draw location by the border insets it doesn't report the same amount of insets in its screen location. Checked in to 1811q, revision 11028. Constantin, please review.

The changes are OK, they fix the drawing issues I was seeing.

#1246 Updated by Constantin Asofiei over 8 years ago

1181q rev 11027 fixes:
  1. ENTRY/LEAVE when the combo's drop-down is active
  2. editor key process re-drawing
  3. combobox: if the mouse is hovering over the drop-down, the element is NOT selected in the parent combo, in 4GL. Only click selects it.
1181q rev 11029 fixes:
  1. TC.sendEntry/Leave must protect the setInvalidate in a finally block (otherwise there are problems when switching focus from a drop-down to some other widget)
  2. window's boundaries start from (0,0).
  3. misc issues for event processing in GUI combo/fill-in/editor, after the ENTRY/LEAVE are generated on mouse actions
  4. drawing optimizations to reduce with ~20% the number of operations sent to JS:
    - starting translation for a draw with a new relative origin of (0,0) is a no-op
    - if origin was not translated and the same clipping rectangle is posted, it is ignored
    - line stroke is sent only once if the insets are the same.
  5. window resizing issues; note that there is still a problem when trying to resize the window more than its max size
  6. the message area background issue in GUI
  7. Web client: EWS.repaint() is problematic when used in window maximize/location change, in the web client. This triggers sending the operations to JS, but if a batch drawing is started, it causes deadlocks as the drawing and the repaint in Web is done in the same thread - so it can't wait for END_BATCH to be received, as there is no one to send it. This is not fixed yet, so window move/maximize/restore/resize/minimize in Web client is not working (and maybe other cases when repaint() is called explicitly). A solution would be to post the drawing operations into a separate thread, and this will be responsible of sending them to the JS side, in the proper order, after is notified by EWS.repaint() - something similar with the AWT event listening thread. The current fix for the web client is terminate with an IllegalStateException instead of getting in a deadlock (although this is not logged, but "eaten" by some other exception).

#1247 Updated by Sergey Ivanovskiy over 8 years ago

The attached file contains all exception codes from 0 to 4096 that don't satisfy the second condition.

Is this for the Swing client or the JS client?

It is for JS client to implement keys processing from WinKeyboardReader.

#1248 Updated by Constantin Asofiei over 8 years ago

Sergey Ivanovskiy wrote:

The attached file contains all exception codes from 0 to 4096 that don't satisfy the second condition.

Is this for the Swing client or the JS client?

It is for JS client to implement keys processing from WinKeyboardReader.

Which script did you use to produce this table? Also, what is the solution to prevent the default action associated with a key (i.e. CTRL-S is save in browser and we need to send this to 4GL).

#1249 Updated by Hynek Cihlar over 8 years ago

Constantin Asofiei wrote:

  1. window resizing issues; note that there is still a problem when trying to resize the window more than its max size

What is the problem? Window shouldn't be allowed to be resized more than the MAX attributes.

#1250 Updated by Constantin Asofiei over 8 years ago

Hynek Cihlar wrote:

Constantin Asofiei wrote:

  1. window resizing issues; note that there is still a problem when trying to resize the window more than its max size

What is the problem? Window shouldn't be allowed to be resized more than the MAX attributes.

If you drag the right border to the right past the MAX-WIDTH, the client abends and restarts. Haven't got a chance to capture the root exception yet.

#1251 Updated by Hynek Cihlar over 8 years ago

Constantin Asofiei wrote:

Hynek Cihlar wrote:

Constantin Asofiei wrote:

  1. window resizing issues; note that there is still a problem when trying to resize the window more than its max size

What is the problem? Window shouldn't be allowed to be resized more than the MAX attributes.

If you drag the right border to the right past the MAX-WIDTH, the client abends and restarts. Haven't got a chance to capture the root exception yet.

I see. This doesn't seem to happen in Swing, so I guess this is something web specific.

#1252 Updated by Sergey Ivanovskiy over 8 years ago

The attached file contains all exception codes from 0 to 4096 that don't satisfy the second condition.

Is this for the Swing client or the JS client?

It is for JS client to implement keys processing from WinKeyboardReader.

Which script did you use to produce this table? Also, what is the solution to prevent the default action associated with a key (i.e. CTRL-S is save in browser and we need to send this to 4GL).

The following java code performed in the GUI Swing client context is used to generate the exception list with all codes that have labels and don't satisfy the condition if the shift modifier is applied to the original code than the label for the modified code ends with the original label without "CTRL-" prefix.

               for (int i = 0; i < 4096; i++ )
               {
                  if (i == KeyEvent.VK_TAB)
                  {
                     continue;
                  }
                  int orig = i;
                  String lblOrig = Keyboard.keyLabel(orig);
                  if (lblOrig.startsWith("CTRL-"))
                  {
                     lblOrig = lblOrig.substring("CTRL-".length());
                  }
                  // modify the next character (in GUI 4GL, SHIFT is a modifier key)
                  int code = i | 1 << 9;
                  String lbl = Keyboard.keyLabel(code);
                  if (!lbl.endsWith("-" + lblOrig) && i != code && lbl.length() > 0)
                  {
                     System.out.println("code: " + orig + " label: " + lblOrig
                              + " modified: " + code + " label:" + lbl);
                  }
               }

This method from testKey.html can be used to prevent the default action
function suppressdefault(e,flag)
{
   if (flag)
   {
       if (e.preventDefault) e.preventDefault();
       if (e.stopPropagation) e.stopPropagation();
   }
   return !flag;
}

Is it okay?

#1253 Updated by Sergey Ivanovskiy over 8 years ago

Constantin Asofiei wrote:

The code in processShift tries to solve cases when the user presses for example CTRL-SHIFT-4, but there is no key with this label in 4GL. At the time when I coded this I was thinking that even if SHIFT is pressed, the key code returned must be CTRL-4, not CTRL-SHIFT-4, as there is no key with this label. But adding a ANY-KEY ANYWHERE trigger and pressing CTRL-SHIFT-4 produces the code 2612, which is key 4 with the CTRL and SHIFT modifiers set. So there might be a problem in WinKeyboardReader.processShift for this case.

But in the other case (where the suffix is checked) this is needed because pressing CTRL first, followed by SHIFT and a letter (for example a), it raises a key code of 1 in 4GL, corresponding for CTRL-A - so the SHIFT modifier is ignored. The rule in processShift can also be interpreted as "if, the labels computed from the key with the CTRL modifier and with the CTRL-SHIFT modifier are not the same after stripping the CTRL and CTRL-SHIFT from the label, then the SHIFT key is ignored". Although it doesn't work in all cases, as there are exceptions to this rule.

Constantin, what is the cases where it doesn't work? It confuses me because it follows that WinKeyboardReader implementation is not correct.
From the documentation ( dvpin.pdf you advised to study) it follows there is the base key set encoded with 9 bits and any combination CTRL, ALT or ESC, SHIFT and a base key is encoded as 16 bits word and CTRL modifier sets bit 11, ALT modifier sets bit 10 and SHIFT modifier sets bit 9. But there are exceptions from this schema:
  1. If you press CTRL plus an alphabetic character or the (@, \, ], ^, or _ special characters), the AVM turns the CTRL key modifier off and maps to the appropriate character code. For example, CTRL+A is mapped to keycode 1, which accords with the ASCII standard (and therefore most 7–bit character code pages).
  2. If you press SHIFT plus a code page character, the AVM turns off the SHIFT key modifier and maps to the appropriate character code. For example, SHIFT+A is equivalent to A, and A is mapped to keycode 65, which accords with the ASCII standard (and therefore most 7–bit character code pages). Also, many code page characters (for example, the comma) do not have uppercase equivalents; therefore, the SHIFT key modifier is turned off.
  3. If you press ALT plus an alphabetic character, the AVM maps to the uppercase alphabetic characters. For example, ALT+A and ALT+a are both mapped to keycode 1089. The keycode 1089 corresponds to the keycode 65 (A) with the ALT key modifier turned on (65 + 1024 = 1089).

In the current 1811 code the web client code handles 1) (function processCtrl(evt, code) defined by p2j.keymap.js), 2) (function mapKey(evt) defined by p2j.keymap.js) and 3) (function onkeydown(evt) defined by p2j.keyboard.js) cases.

#1254 Updated by Greg Shah over 8 years ago

Rebased task branch 1811q from P2J trunk revision 10938. Latest revision is now 11030.

#1255 Updated by Constantin Asofiei over 8 years ago

Sergey Ivanovskiy wrote:

Constantin Asofiei wrote:

The code in processShift tries to solve cases when the user presses for example CTRL-SHIFT-4, but there is no key with this label in 4GL. At the time when I coded this I was thinking that even if SHIFT is pressed, the key code returned must be CTRL-4, not CTRL-SHIFT-4, as there is no key with this label. But adding a ANY-KEY ANYWHERE trigger and pressing CTRL-SHIFT-4 produces the code 2612, which is key 4 with the CTRL and SHIFT modifiers set. So there might be a problem in WinKeyboardReader.processShift for this case.

But in the other case (where the suffix is checked) this is needed because pressing CTRL first, followed by SHIFT and a letter (for example a), it raises a key code of 1 in 4GL, corresponding for CTRL-A - so the SHIFT modifier is ignored. The rule in processShift can also be interpreted as "if, the labels computed from the key with the CTRL modifier and with the CTRL-SHIFT modifier are not the same after stripping the CTRL and CTRL-SHIFT from the label, then the SHIFT key is ignored". Although it doesn't work in all cases, as there are exceptions to this rule.

Constantin, what is the cases where it doesn't work? It confuses me because it follows that WinKeyboardReader implementation is not correct.

You are correct, the WinKeyboardReader is flawed here. I think I tried to do an over-simplification of the documented rules and looks like it didn't work. Please adjust the WinKeyboardReader to work as described in dvpin.pdf.

About the suppressdefault code you posted - I'm OK with it.

#1256 Updated by Greg Shah over 8 years ago

Sergey: please update the javadoc and/or comments for WinKeyboardReader (and your new p2j.key*.js files) to include the information from note 1253. If it is still relevant (after your changes), also include the details from Constantin's note 1243.

The objective is to ensure that future readers don't have to ask questions about how that code works.

#1257 Updated by Greg Shah over 8 years ago

Sergey Ivanovskiy wrote:

There is another issue with SET_WINDOW_BOUNDS drawing primitive. The last one breaks the order of drawing operations for the web client. If the attached diff is applied, then the translate out of order errors appear.
[...]

Revision 11031 has a fix for SET_WINDOW_BOUNDS (JS side). The width and height parsing was using the wrong indexes. It was using 1 and 5 instead of 9 and 13.

But I'm unsure how to recreate the translate out of order issue, so I haven't checked to see if this was the cause. It could be, if there is an abend that causes the drawing to abort early.

Sergey: can you check this? If it is still a problem, please post how to recreate it.

#1258 Updated by Sergey Ivanovskiy over 8 years ago

Revision 11031 has a fix for SET_WINDOW_BOUNDS (JS side). The width and height parsing was using the wrong indexes. It was using 1 and 5 instead of 9 and 13.

But I'm unsure how to recreate the translate out of order issue, so I haven't checked to see if this was the cause. It could be, if there is an abend that causes the drawing to abort early.

Sergey: can you check this? If it is still a problem, please post how to recreate it.

It is my copy-paste mistake. But I could't check it now because after the p2j client window is displayed (web browser), the application fails with this server exception:
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.goldencode.p2j.util.Utils.invoke(Utils.java:1252)
at com.goldencode.p2j.main.StandardServer$MainInvoker.execute(StandardServer.java:1767)
at com.goldencode.p2j.main.StandardServer.invoke(StandardServer.java:1270)
at com.goldencode.p2j.main.StandardServer.standardEntry(StandardServer.java:427)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.goldencode.p2j.util.MethodInvoker.invoke(MethodInvoker.java:76)
at com.goldencode.p2j.net.Dispatcher.processInbound(Dispatcher.java:694)
at com.goldencode.p2j.net.Conversation.block(Conversation.java:319)
at com.goldencode.p2j.net.Conversation.run(Conversation.java:163)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.RuntimeException: No renderer is registered for id = -45
at com.goldencode.p2j.ui.client.gui.driver.GuiPrimitivesImpl.getWindowEmulator(GuiPrimitivesImpl.java:203)
at com.goldencode.p2j.ui.client.gui.driver.GuiPrimitivesImpl.selectWindow(GuiPrimitivesImpl.java:270)
at com.goldencode.p2j.ui.client.gui.driver.AbstractGuiDriver.selectWindow(AbstractGuiDriver.java:1519)
at com.goldencode.p2j.ui.client.gui.ModalWindow.init(ModalWindow.java:542)
at com.goldencode.p2j.ui.client.gui.AlertBoxGuiImpl.init(AlertBoxGuiImpl.java:274)
at com.goldencode.p2j.ui.client.gui.AlertBoxGuiImpl.<init>(AlertBoxGuiImpl.java:113)
at com.goldencode.p2j.ui.client.gui.GuiWidgetFactory.createAlertBox(GuiWidgetFactory.java:315)
at com.goldencode.p2j.ui.client.gui.GuiWidgetFactory.createAlertBox(GuiWidgetFactory.java:72)
at com.goldencode.p2j.ui.chui.ThinClient$12.run(ThinClient.java:6673)
at com.goldencode.p2j.ui.chui.ThinClient.eventBracket(ThinClient.java:13523)
at com.goldencode.p2j.ui.chui.ThinClient.eventBracket(ThinClient.java:13496)
at com.goldencode.p2j.ui.chui.ThinClient.messageBox(ThinClient.java:6667)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.goldencode.p2j.util.MethodInvoker.invoke(MethodInvoker.java:76)
at com.goldencode.p2j.net.Dispatcher.processInbound(Dispatcher.java:694)
at com.goldencode.p2j.net.Conversation.block(Conversation.java:319)
at com.goldencode.p2j.net.Conversation.waitMessage(Conversation.java:257)
at com.goldencode.p2j.net.Queue.transactImpl(Queue.java:1128)
at com.goldencode.p2j.net.Queue.transact(Queue.java:585)
at com.goldencode.p2j.net.BaseSession.transact(BaseSession.java:223)
at com.goldencode.p2j.net.HighLevelObject.transact(HighLevelObject.java:163)
at com.goldencode.p2j.net.RemoteObject$RemoteAccess.invokeCore(RemoteObject.java:1425)
at com.goldencode.p2j.net.InvocationStub.invoke(InvocationStub.java:97)
at com.sun.proxy.$Proxy11.standardEntry(Unknown Source)
at com.goldencode.p2j.main.ClientCore.start(ClientCore.java:277)
at com.goldencode.p2j.main.ClientCore.start(ClientCore.java:100)
at com.goldencode.p2j.main.ClientDriver.start(ClientDriver.java:201)
at com.goldencode.p2j.main.CommonDriver.process(CommonDriver.java:398)
at com.goldencode.p2j.main.ClientDriver.process(ClientDriver.java:95)
at com.goldencode.p2j.main.ClientDriver.main(ClientDriver.java:267)
[09/22/2015 19:50:49 MSK] (Incoming.run():WARNING) {Incoming SSL Connector} initialization failure
javax.net.ssl.SSLException: Connection has been shutdown: javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_unknown
at sun.security.ssl.SSLSocketImpl.checkEOF(SSLSocketImpl.java:1529)
at sun.security.ssl.SSLSocketImpl.checkWrite(SSLSocketImpl.java:1541)
at sun.security.ssl.AppOutputStream.write(AppOutputStream.java:71)
at java.io.ObjectOutputStream$BlockDataOutputStream.drain(ObjectOutputStream.java:1877)
at java.io.ObjectOutputStream$BlockDataOutputStream.setBlockDataMode(ObjectOutputStream.java:1786)
at java.io.ObjectOutputStream.<init>(ObjectOutputStream.java:247)
at com.goldencode.p2j.net.NetSocket.<init>(NetSocket.java:79)
at com.goldencode.p2j.net.RouterSessionManager$Incoming.run(RouterSessionManager.java:1389)
at java.lang.Thread.run(Thread.java:745)
Caused by: javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_unknown
at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
at sun.security.ssl.Alerts.getSSLException(Alerts.java:154)
at sun.security.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.java:2011)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1113)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1363)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1391)
at sun.security.ssl.SSLSocketImpl.getSession(SSLSocketImpl.java:2225)
at com.goldencode.p2j.net.NetSocket.<init>(NetSocket.java:75)
at com.goldencode.p2j.net.RouterSessionManager$Incoming.run(RouterSessionManager.java:1389)
at java.lang.Thread.run(Thread.java:745)

#1259 Updated by Sergey Ivanovskiy over 8 years ago

And the client side produces
javax.net.ssl.SSLException: Connection has been shutdown: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: No trusted certificate found
at sun.security.ssl.SSLSocketImpl.checkEOF(SSLSocketImpl.java:1529)
at sun.security.ssl.SSLSocketImpl.checkWrite(SSLSocketImpl.java:1541)
at sun.security.ssl.AppOutputStream.write(AppOutputStream.java:71)
at java.io.ObjectOutputStream$BlockDataOutputStream.drain(ObjectOutputStream.java:1877)
at java.io.ObjectOutputStream$BlockDataOutputStream.setBlockDataMode(ObjectOutputStream.java:1786)
at java.io.ObjectOutputStream.<init>(ObjectOutputStream.java:247)
at com.goldencode.p2j.net.NetSocket.<init>(NetSocket.java:79)
at com.goldencode.p2j.net.SessionManager.createQueue(SessionManager.java:1036)
at com.goldencode.p2j.net.LeafSessionManager.connectDirect(LeafSessionManager.java:201)
at com.goldencode.p2j.net.SessionManager.connectDirect(SessionManager.java:587)
at com.goldencode.p2j.main.ClientCore.start(ClientCore.java:196)
at com.goldencode.p2j.main.ClientCore.start(ClientCore.java:100)
at com.goldencode.p2j.main.ClientDriver.start(ClientDriver.java:201)
at com.goldencode.p2j.main.CommonDriver.process(CommonDriver.java:398)
at com.goldencode.p2j.main.ClientDriver.process(ClientDriver.java:95)
at com.goldencode.p2j.main.ClientDriver.main(ClientDriver.java:267)
Caused by: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: No trusted certificate found
at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1937)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:302)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:296)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1478)
at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:212)
at sun.security.ssl.Handshaker.processLoop(Handshaker.java:979)
at sun.security.ssl.Handshaker.process_record(Handshaker.java:914)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1050)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1363)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1391)
at sun.security.ssl.SSLSocketImpl.getSession(SSLSocketImpl.java:2225)
at com.goldencode.p2j.net.NetSocket.<init>(NetSocket.java:75)
... 9 more
Caused by: sun.security.validator.ValidatorException: No trusted certificate found
at sun.security.validator.SimpleValidator.buildTrustedChain(SimpleValidator.java:384)
at sun.security.validator.SimpleValidator.engineValidate(SimpleValidator.java:133)
at sun.security.validator.Validator.validate(Validator.java:260)
at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:324)
at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:229)
at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:124)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1460)
... 17 more
Does it exist only for my configuration?

#1260 Updated by Sergey Ivanovskiy over 8 years ago

I bet that only HelloWorld example works properly now for swing client and for web client.

#1261 Updated by Greg Shah over 8 years ago

You may be right. I can run hello.p without errors in both Swing and Web.

I've been debugging button/gui_btn_test4.p. That fails earlier than your testcase. I did see this (in the Swing client) at the end of the log:

java.lang.RuntimeException: No renderer is registered for id = 11
        at com.goldencode.p2j.ui.client.gui.driver.GuiPrimitivesImpl.getWindowEmulator(GuiPrimitivesImpl.java:203)
        at com.goldencode.p2j.ui.client.gui.driver.GuiPrimitivesImpl.selectWindow(GuiPrimitivesImpl.java:270)
        at com.goldencode.p2j.ui.client.gui.driver.AbstractGuiDriver.selectWindow(AbstractGuiDriver.java:1519)
        at com.goldencode.p2j.ui.client.gui.driver.swing.SwingGuiDriver.restackWindows(SwingGuiDriver.java:399)
        at com.goldencode.p2j.ui.client.gui.driver.AbstractGuiDriver.stackWindows(AbstractGuiDriver.java:1816)
        at com.goldencode.p2j.ui.client.gui.WindowGuiImpl.hide(WindowGuiImpl.java:248)
        at com.goldencode.p2j.ui.client.WindowManager.clearWindowList(WindowManager.java:230)
        at com.goldencode.p2j.ui.chui.ThinClient.terminate(ThinClient.java:2850)
        at com.goldencode.p2j.main.ClientCore.start(ClientCore.java:280)
        at com.goldencode.p2j.main.ClientCore.start(ClientCore.java:100)
        at com.goldencode.p2j.main.ClientDriver.start(ClientDriver.java:201)
        at com.goldencode.p2j.main.CommonDriver.process(CommonDriver.java:398)
        at com.goldencode.p2j.main.ClientDriver.process(ClientDriver.java:95)
        at com.goldencode.p2j.main.ClientDriver.main(ClientDriver.java:267)

It appears at the end of the log file but that was after the client was already exiting due to another bug, so I had dismissed it as just something broken because of the other problem.

What testcase are you trying?

#1262 Updated by Sergey Ivanovskiy over 8 years ago

First tried the attached test and then started hello world and demo_widget, the standard testcases/uast/demo/demo_widgets.p works properly and translate out of order errors aren't reproduced. I understand now that it is due to the modal dialogs are not implemented.(it works only for CHUI)

#1263 Updated by Sergey Ivanovskiy over 8 years ago

Committed revision 11032 removes unnecessary repaints of the task bar. It needs to draw if the title or icon or its visible state or its active states is changed.

#1264 Updated by Sergey Ivanovskiy over 8 years ago

Committed revision 11033 implements the logic from WinKeyboardReader. Planning to use Selenium to enumerate all keyboard keys combinations and to test the produced codes match Progress 4GL.

#1265 Updated by Sergey Ivanovskiy over 8 years ago

Committed revision 11034 fixes the 11032 (task 1811 note 1263).

#1266 Updated by Greg Shah over 8 years ago

From Sergey:

From the documentation ( dvpin.pdf you advised to study) it follows there is the base key set encoded with 9 bits and any combination CTRL, ALT or ESC, SHIFT and a base key is encoded as 16 bits word and CTRL modifier sets bit 11, ALT modifier sets bit 10 and SHIFT modifier sets bit 9. But there are exceptions from this schema:

If you press CTRL plus an alphabetic character or the (@, \, ], ^, or _ special characters), the AVM turns the CTRL key modifier off and maps to the appropriate character code. For example, CTRL+A is mapped to keycode 1, which accords with the ASCII standard (and therefore most 7–bit character code pages).
If you press SHIFT plus a code page character, the AVM turns off the SHIFT key modifier and maps to the appropriate character code. For example, SHIFT+A is equivalent to A, and A is mapped to keycode 65, which accords with the ASCII standard (and therefore most 7–bit character code pages). Also, many code page characters (for example, the comma) do not have uppercase equivalents; therefore, the SHIFT key modifier is turned off.
If you press ALT plus an alphabetic character, the AVM maps to the uppercase alphabetic characters. For example, ALT+A and ALT+a are both mapped to keycode 1089. The keycode 1089 corresponds to the keycode 65 (A) with the ALT key modifier turned on (65 + 1024 = 1089).

Because of #1811-1255 I need to check all symbols with modifiers on windows Progress 4GL
to build a table with columns for symbols and modifiers: ctrl, alt, shift mapped to Progress 4GL codes and then use it for key processing. Now the web client for this combination CTRL + SHIFT + '4' produces 36($ for US keyboard) as SHIFT + '4'. Should it be 2048 + 512 + 52 = 2612?
Is there a way to generate the table from Progress
symbol , ctrl, alt, shift - Progress 4GL code
'a' ....
.....
'z' .....
'A'.....

'Z'.....
0 .....
......
9 .....
.......

#1267 Updated by Greg Shah over 8 years ago

From Constantin:

Now the web client for this combination CTRL + SHIFT + '4' produces 36($ for US keyboard) as SHIFT + '4'. Should it be 2048 + 512 + 52 = 2612?

Yes, CTR+SHIFT+'4' produces 2612 code in 4GL. Use this testcase on windev01 and "on screen keyboard":
ON ANY-KEY ANYWHERE
DO:
MESSAGE LAST-EVENT:CODE LAST-EVENT:LABEL.
RETURN.
END.

DEF VAR ch AS CHAR.
UPDATE ch WITH FRAME f1.

I think the problem is the browser processes the SHIFT+<key> before CTRL is applied, instead of processing all keys as one single combination. Do you know if there is a way to find which is the key's SHIFT counterpart, for the current keyboard layout (i.e. $ for key 4, in US keyboard) - maybe some event property? Or some other way to allow SHIFT to be processed until after a non-modifier key was pressed/typed, in Javascript?

Is there a way to generate the table from Progress?

There is no good to do this in a 4GL script, as you will not be able to simulate real CTRL/SHIFT/ALT press. What might work is some Win32 program which simulates sending ctrl/shift/alt/key combinations to a 4GL program, which in turn captures it and logs the KEY-LABEL and LASTKEY codes to a file. But I have no idea how to make the Win32 program.

As Greg noted, lets do this in phases: first phase we should provide support for all key codes specified in the client.event.Key class - these for sure are used in the P2J runtime. The second phase will take care of the other issues.

#1268 Updated by Greg Shah over 8 years ago

How much more work is remaining in the "first phase" of key processing?

#1269 Updated by Sergey Ivanovskiy over 8 years ago

I would like to compare the codes produced by GuiKeyboardReader with Progress 4GL key codes (./testcases/uast/keyboards/win-gui.txt) using Selenium.

#1270 Updated by Hynek Cihlar over 8 years ago

Checked in revision 11035 resolves the abend reported in #2700 note 5.

#1271 Updated by Vadim Gindin over 8 years ago

fix NPE from #2707, fix small drawing menu bug for CHUI: mnemonic for the focused menu-item is displayed without underline, fix NPE in menu GUI impls. Committed in revision 11037.

#1272 Updated by Greg Shah over 8 years ago

Rebased task branch 1811q from P2J trunk revision 10939. The latest revision is now 11039.

#1273 Updated by Sergey Ivanovskiy over 8 years ago

Committed revision 11040 has minor changes for the key test.
In the attachment there is the first part of the key test for the first codes from 0 to 31 with CTRL modifier. It proves that CHUI passes it but GUI not. I need to fix it shortly. The test screen contains two text area. The first area is used for the keys capture and the second one is for the report. Planning to extend this test to cover all codes from 0 to 4096.

#1274 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811q Revision 11041

After much cleanup, this version seems like a release candidate. We do have some EmulatedWindowState.drawOnScreenBitmap() logging that probably should be made conditional. But I think it is OK for now, because it only affects GUI code. I think it is useful for debugging for now.

I'm going to start regression testing on this. If we have regressions found in testing, then I'll "reopen" 1811q, but for now, consider 1811q "locked".

Sergey: I know you may have some key processing fixes to include. Plan to include them in 1811r for now.

#1275 Updated by Sergey Ivanovskiy over 8 years ago

Okay, the attached file contains fixes for the key codes from 0 to 31 (CTRL + *).

#1276 Updated by Hynek Cihlar over 8 years ago

BorderedPanelGuiImpl incorrectly translates its location twice, first and correct before the insets/border are accounted for and second incorrect after the insets/border to prepare for content drawing. The second translation should offset only by insets and not physicalLocation().translate(ninsets.left, ninsets.top). I am a bit confused why this wasn't more apparent.

#1277 Updated by Sergey Ivanovskiy over 8 years ago

Greg, could you permit me to commit the keyboard changes? This is important because for example TAB button is fixed and the keyboard test is approved, new function simulateKeyPressExt("testArea", 220, 92, true, false, false, false) is added that is used if the browser key code is different from the typed code. For example, if we press the button '\|', the browser code will be 220, but the typed code will be 92 ('\').

#1278 Updated by Greg Shah over 8 years ago

I got 2 failures in the main runtime regression run. They are probably false negatives. I'm just about to start another run.

Your changes are in JS files that don't affect MAJIC runtime regression testing. Go ahead with the check in. It won't invalidate my testing so far. After you check this in, consider the branch locked again.

#1279 Updated by Sergey Ivanovskiy over 8 years ago

Committed revision 11042 fixes 0...31 codes generated by CTRL + . key sequence and TAB, SHIFT-TAB according to the passed test.

#1280 Updated by Greg Shah over 8 years ago

Regression testing has been restarted. On the last run, only GSO 237 and tc_codes_employees_21 failed.

#1281 Updated by Sergey Ivanovskiy over 8 years ago

Hi Constantin,

No, I don't know how to find for a key its SHIFT counterpart but it is possible to generate all codes for the target range from 0 to 4096 and then use this static map, may be it can be simplified (packed) using some common patterns.

Sergey.

On 23.09.2015 12:36, Constantin Asofiei wrote:

Sergey,

Now the web client for this combination CTRL + SHIFT + '4' produces 36($ for US keyboard) as SHIFT + '4'. Should it be 2048 + 512 + 52 = 2612?

Yes, CTR+SHIFT+'4' produces 2612 code in 4GL. Use this testcase on windev01 and "on screen keyboard":
ON ANY-KEY ANYWHERE
DO:
MESSAGE LAST-EVENT:CODE LAST-EVENT:LABEL.
RETURN.
END.

DEF VAR ch AS CHAR.
UPDATE ch WITH FRAME f1.

I think the problem is the browser processes the SHIFT+<key> before CTRL is applied, instead of processing all keys as one single combination. Do you know if there is a way to find which is the key's SHIFT counterpart, for the current keyboard layout (i.e. $ for key 4, in US keyboard) - maybe some event property? Or some other way to allow SHIFT to be processed until after a non-modifier key was pressed/typed, in Javascript?

#1282 Updated by Constantin Asofiei over 8 years ago

Sergey, the problem here is that if you press CTRL-SHIFT-4 and the browser sends CTRL-$ (with or without the SHIFT modifier on) instead of CTRL-SHIFT-4, we will not be able produce the correct code.

How do you plan to generate this static map: use a script (Selenyum?) to send all key codes in 0...4096 range to the browser and see what the browser decodes?

#1283 Updated by Constantin Asofiei over 8 years ago

Greg/Sergey: I found the clipping problem in JS. For the Swing client, setting a new clip region means intersecting it with the current clip region. From Graphics.clipRect:

Intersects the current clip with the specified rectangle. The resulting clipping area is the intersection of the current clipping area and the specified rectangle. If there is no current clipping area, either because the clip has never been set, or the clip has been cleared using setClip(null), the specified rectangle becomes the new clip. This method sets the user clip, which is independent of the clipping associated with device bounds and window visibility. This method can only be used to make the current clip smaller. To set the current clip larger, use any of the setClip methods. Rendering operations have no effect outside of the clipping area.

In CanvasRenderer.clip, we are not intersecting... canvas.clip just sets the new clipping region. I will fix this by checking the previous clipped region in the stack, intersect, and use that as clipping.

The somehow good news is that this clipping problem in JS exposed some sizing issues related to drop-down/scrollbars... still working on why they are happening.

#1284 Updated by Greg Shah over 8 years ago

In regard to capturing more data about how the 4GL handles its key processing, we can write a WIN32 program to help us feed input into a running 4GL GUI program. That 4GL GUI code can have any-key anywhere triggers that write out the key data so that we can see how the 4GL interprets the corresponding input.

By writing a WIN32 program to do this, we can avoid a manually intensive and error prone capture process (where the user must manually enter each key sequence into a 4GL GUI program.

1. As a template for writing a standalone WIN32 program in C, please see tools/dump_syscolor.c. Here is the core of that code:

// On Windows, you must install mingw from http://mingw-w64.sourceforge.net or
// from http://www.drangon.org/mingw. Take one of the full archives:
// mingw-w32-bin-i686-2013MMDD.7z or mingw-w64-bin-x86_64-2013MMDD.7z and unpack
// it to c:\.  Then include The c:\mingw32\bin\ or c:\mingw64\bin\ to the PATH
// variable, depending on the Windows architecture in use. We have not had good
// luck using the automatic installer for mingw.

// Then compile using this:
// gcc dump_syscolor.c -o dump_syscolor.exe

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

void dumpColor(char* name, int num)
{
   // see API docs at https://msdn.microsoft.com/en-us/library/windows/desktop/ms724371%28v=vs.85%29.aspx
   DWORD color = GetSysColor(num);

   printf("%s (%d) = 0x%08X (%d, %d, %d)\n",
          name,
          num,
          color,
          GetRValue(color),
          GetGValue(color),
          GetBValue(color));  
}

void main(int argc, char* argv)
{
   dumpColor("COLOR_SCROLLBAR", COLOR_SCROLLBAR);
   dumpColor("COLOR_BACKGROUND", COLOR_BACKGROUND);
...
}

In windev01, the MINGW compiler is already properly setup. So this program and others like it can be compiled right now.

Additional includes may be needed depending on the APIs and data structures being used. The printf() comes from the C library and just outputs to STDOUT. So when you run dump_syscolor.exe, you will see the output on the console (and you can redirect to file to capture it).

2. WIN32 has the ability to to artificially feed key input into a program. Some of the most useful references are here:

About Keyboard Input
Simulating Input
keybd_event() function
SendInput() function
INPUT structure
KEYBDINPUT structure
Virtual Key Codes

Using this approach, one can inject keys into the WIN32-level keyboard event queue for the current thread. The trick here is that the 4GL program will probably need to execute our send program as a child process. There should already be a FILL-IN that is enabled and has the proper trigger. The focus must be in that FILL-IN. So long as our send program doesn't open a window that can accept input, I think the events will be routed to the focused FILL-IN.

3. The other trick here is in figuring out the low-level INPUT or KEYBDINPUT data structures for each case you want to submit. You might have to run a simple WIN32 program that has a text entry field and can dump the keyboard messages (e.g. WM_KEYDOWN or WM_CHAR) out for your inspection. I don't recall how the CTRL and SHIFT keys get processed, but I think ALT is included in the WM_CHAR message flags. It may be that the application itself must track if CTRL or SHIFT are pressed (a WM_KEYDOWN was rec'd for one of these without a corresponding WM_KEYUP).

https://msdn.microsoft.com/en-us/library/windows/desktop/ms646267%28v=vs.85%29.aspx#_win32_Keystroke_Messages

An easier approach is to use an open source "message spy" tool. They are not illegal. :) It just watches the message queue for another process and dumps the messages. This is really useful when developing GUI code for WIN32 (or OS/2, we used to have a similar tool). The Spy++ comes in the Microsoft SDK for this very purpose. Here is an open source tool (GPL) that may work:

http://www.codeproject.com/Articles/33459/Spying-Window-Messages-from-the-Inside

With this tool, you can run a simple 4GL GUI program and as you type input into an enabled FILL-IN, you can see the WIN32 messages that are sent in response to your input.

Let me know if you have any questions.

#1285 Updated by Sergey Ivanovskiy over 8 years ago

This schema of the Progress 4GL keys capture has a JS analog. http://unixpapa.com/js/testkey.html has a text area that catches keystrokes the firefox generates for JS. And test.html simulates the keystrokes for the text area and intercepts our keys processing to log the results. But Progress 4GL codes are from win-gui.txt. Thus using your schema we can build the reverse map to win-gui.txt that maps Progress 4GL code to the corresponding keystrokes.

#1286 Updated by Greg Shah over 8 years ago

Main runtime regression testing has passed. CTRL-C testing is starting now.

#1287 Updated by Greg Shah over 8 years ago

CTRL-C testing has completed successfully.

Task branch 1811q has been merged into P2J trunk as revision 10940.

Task branch 1811q has been archived.

#1288 Updated by Greg Shah over 8 years ago

Created task branch 1811r from P2J trunk revision 10940.

#1289 Updated by Greg Shah over 8 years ago

Hynek Cihlar wrote:

BorderedPanelGuiImpl incorrectly translates its location twice, first and correct before the insets/border are accounted for and second incorrect after the insets/border to prepare for content drawing. The second translation should offset only by insets and not physicalLocation().translate(ninsets.left, ninsets.top). I am a bit confused why this wasn't more apparent.

Do you have a fix? Please apply it to 1811r.

#1290 Updated by Constantin Asofiei over 8 years ago

Greg/Sergey: I found the cause of the leaked drawing in JS code. Looks like the putImageData function is not aware of the clipped region. See attached for an example: the green line on the top should have been limited to the clipped rectangle (0,0) - (200, 200), but instead it goes outside of it; the clip is in effect, as a fillRect(100,100,400,400) gets clipped.

I'll try to just ignore putImageData with coordinates outside of the last posted clipped rectangle.

#1291 Updated by Greg Shah over 8 years ago

Looks like the putImageData function is not aware of the clipped region.

Nice find!

#1292 Updated by Constantin Asofiei over 8 years ago

In 1811r rev 10941 following issues are addressed (if not mentioned, it affects both Web and Swing drivers):
  1. window iconification abend
  2. posting mouse events with explicit source: this is required to properly manage mouse enter/exit for P2J widgets
  3. misc fixes related to focus switch
  4. fixes to reduce/eliminate duplicated drawing
  5. fixes related to z-order for Web clients
  6. system menu abend
  7. in the Web driver, adding a new clip rectangle requires intersecting it with the last clip rectangle
  8. in the Web driver, putImageData needs to check the current clip first, and if is outside of it, do not draw the image.
  9. fixed "window resize" cursor restoration in Web driver
  10. fixed "window resize" cursor restoration in Swing driver + window resize
Note that with the current clipping changes in AbstractWidget.draw, you might see artifacts in both Swing and Web; this is most likely caused by PaintEvent's raised with improper coordinates, which do not match the area being drawn; the known list at this time is:
  1. scroll bar buttons when pressed
  2. scroll bar for EDITOR looks like is drawn a couple of pixels outside of the EDITOR
  3. scroll bar buttons for MESSAGE-AREA are shifted to the left after WINDOW-MAXIMIZE button is pressed twice (i.e. window maximized then restored).
  4. combo-box drop-down artifact

#1293 Updated by Sergey Ivanovskiy over 8 years ago

Constantin Asofiei wrote:

Sergey, the problem here is that if you press CTRL-SHIFT-4 and the browser sends CTRL-$ (with or without the SHIFT modifier on) instead of CTRL-SHIFT-4, we will not be able produce the correct code.

How do you plan to generate this static map: use a script (Selenyum?) to send all key codes in 0...4096 range to the browser and see what the browser decodes?

It is not possible to use only JS to produce codes generated by firefox on pressing keys combinations, because the events simulated by JS can't produce the browser/OS related events: keydown, keypress and keyup. Thus I need the more complicated technique to generate all firefox generated codes or look at the firefox source, because keyboard OS scan codes are different from the firefox keys codes. The last ones are approximately X11 keystokes, for example http://www.cl.cam.ac.uk/~mgk25/ucs/keysymdef.h and http://www.cl.cam.ac.uk/~mgk25/ucs/X11.keysyms.pdf.
Now I am gathering the firefox scancodes manually.

#1294 Updated by Constantin Asofiei over 8 years ago

Hynek, there is an inconsistency in scrollbar drawing and parenting; the drawing is done using this call chain:

ScrollBarGuiImpl.draw() line: 216    
ScrollPaneGuiImpl$2.drawScrollBars() line: 204    
ScrollPaneGuiImpl$2.run() line: 151    
SwingGuiDriver(AbstractGuiDriver<F>).draw(NativePoint, NativeRectangle, Runnable) line: 1647    
ScrollPaneGuiImpl.draw() line: 142    
DropDownGuiImpl(AbstractContainer<O>).draw() line: 319    
DropDownGuiImpl(TitledWindow<O>).draw() line: 98    
DropDownGuiImpl(OuterFrame<O>).drawInt() line: 98    
DropDownGuiImpl.access$0(DropDownGuiImpl) line: 1    

But the parent chain for the scrollbar is: DropDownGuiImpl -> ScrollPaneGuiImpl -> BorderedPanelGuiImpl -> ScrollBarGuiImpl. This results in a one-pixel shift during drawing. Is this what you are referring in note 1276?

#1295 Updated by Hynek Cihlar over 8 years ago

Constantin Asofiei wrote:

Hynek, there is an inconsistency in scrollbar drawing and parenting; the drawing is done using this call chain:
[...]
But the parent chain for the scrollbar is: DropDownGuiImpl -> ScrollPaneGuiImpl -> BorderedPanelGuiImpl -> ScrollBarGuiImpl. This results in a one-pixel shift during drawing. Is this what you are referring in note 1276?

I don't think this is the case from note 1276. Is is possible that

ScrollBarGuiImpl.draw() line: 216    
ScrollPaneGuiImpl$2.drawScrollBars() line: 204    
ScrollPaneGuiImpl$2.run() line: 151    
SwingGuiDriver(AbstractGuiDriver<F>).draw(NativePoint, NativeRectangle, Runnable) line: 1647    
ScrollPaneGuiImpl.draw() line: 142    

belongs to one of the parent containers?

#1296 Updated by Hynek Cihlar over 8 years ago

Constantin Asofiei wrote:

Hynek, there is an inconsistency in scrollbar drawing and parenting; the drawing is done using this call chain:
[...]
But the parent chain for the scrollbar is: DropDownGuiImpl -> ScrollPaneGuiImpl -> BorderedPanelGuiImpl -> ScrollBarGuiImpl. This results in a one-pixel shift during drawing. Is this what you are referring in note 1276?

The issue 1276 would be apparent when the bordered panel itself was placed at nonzero location. In that case the double-translation could lead to the one-pixel shift under the specific circumstance. Do you have a test case?

Also I fixed a clipping issues in the bordered panel, but that surely isn't this case either.

#1297 Updated by Hynek Cihlar over 8 years ago

Greg Shah wrote:

Hynek Cihlar wrote:

BorderedPanelGuiImpl incorrectly translates its location twice, first and correct before the insets/border are accounted for and second incorrect after the insets/border to prepare for content drawing. The second translation should offset only by insets and not physicalLocation().translate(ninsets.left, ninsets.top). I am a bit confused why this wasn't more apparent.

Do you have a fix? Please apply it to 1811r.

Greg, the fix is ready. It will be ready when I finish the java8 upgrade.

#1298 Updated by Constantin Asofiei over 8 years ago

Hynek Cihlar wrote:

I don't think this is the case from note 1276. Is is possible that ... belongs to one of the parent containers?

No, the parent chain is as I posted it.

Do you have a test case?

See testcases/uast/demo/demo_widgets.p - open a combo's drop-down and you will see what I mean.

Also, the BorderedPanelGuiImpl's size is incorrect - in 4GL, the border is drawn (at least for drop-down and message area) around the scrollbars, too. In P2J, its boundaries exclude it.

#1299 Updated by Hynek Cihlar over 8 years ago

Greg Shah wrote:

Hynek Cihlar wrote:

BorderedPanelGuiImpl incorrectly translates its location twice, first and correct before the insets/border are accounted for and second incorrect after the insets/border to prepare for content drawing. The second translation should offset only by insets and not physicalLocation().translate(ninsets.left, ninsets.top). I am a bit confused why this wasn't more apparent.

Do you have a fix? Please apply it to 1811r.

Checked in to 1811r as revision 10943.

#1300 Updated by Constantin Asofiei over 8 years ago

Followup for notes 1294/1295/1298: the scrollbar drawing is being performed twice, once from BorderedPanelGuiImpl and once from ScrollPaneGuiImpl.drawScrollBars. I think the drawing call from ScrollPaneGuiImpl.drawScrollBars is not needed, but the layout performed there is needed.

Also:
  1. the scrollbar width (for vertical) and height (for horizontal) looks wrong: we compute both as font-height (in pixels), but on windev01 looks like is 16px width/height.
  2. the scrollbar height (for vertical) and width (for horizontal) needs to be aware of the parent's insets. I'm testing a fix for this.
  3. another issue I don't like is button press on scrollbar - it doesn't look right.

#1301 Updated by Hynek Cihlar over 8 years ago

Constantin Asofiei wrote:

Followup for notes 1294/1295/1298: the scrollbar drawing is being performed twice, once from BorderedPanelGuiImpl and once from ScrollPaneGuiImpl.drawScrollBars. I think the drawing call from ScrollPaneGuiImpl.drawScrollBars is not needed, but the layout performed there is needed.

Also:
  1. the scrollbar width (for vertical) and height (for horizontal) looks wrong: we compute both as font-height (in pixels), but on windev01 looks like is 16px width/height.

On Windows/classic theme the scrollbar width (for vertical) and height (for horizontal) is taken from the theme settings.

  1. another issue I don't like is button press on scrollbar - it doesn't look right.

Do you mean the pressed button being drawn only partially? I noticed this after the merge of 2424c/2424d into 2811q.

#1302 Updated by Constantin Asofiei over 8 years ago

Hynek Cihlar wrote:

Constantin Asofiei wrote:

Followup for notes 1294/1295/1298: the scrollbar drawing is being performed twice, once from BorderedPanelGuiImpl and once from ScrollPaneGuiImpl.drawScrollBars. I think the drawing call from ScrollPaneGuiImpl.drawScrollBars is not needed, but the layout performed there is needed.

Also:
  1. the scrollbar width (for vertical) and height (for horizontal) looks wrong: we compute both as font-height (in pixels), but on windev01 looks like is 16px width/height.

On Windows/classic theme the scrollbar width (for vertical) and height (for horizontal) is taken from the theme settings.

  1. another issue I don't like is button press on scrollbar - it doesn't look right.

Do you mean the pressed button being drawn only partially? I noticed this after the merge of 2424c/2424d into 2811q.

No, I've fixed the partially drawn issue, is something else. Some positioning (or maybe even clipping rectangle?) not set properly.

I'll check in my changes shortly.

#1303 Updated by Sergey Ivanovskiy over 8 years ago

From Constantin:

Now the web client for this combination CTRL + SHIFT + '4' produces 36($ for US keyboard) as SHIFT + '4'. Should it be 2048 + 512 + 52 = 2612?

Yes, CTR+SHIFT+'4' produces 2612 code in 4GL. Use this testcase on windev01 and "on screen keyboard":
ON ANY-KEY ANYWHERE
DO:
MESSAGE LAST-EVENT:CODE LAST-EVENT:LABEL.
RETURN.
END.

DEF VAR ch AS CHAR.
UPDATE ch WITH FRAME f1.

Currently I have tried this program on windev01 and found the unexpected behaviour if CTRL+SHIFT+A is pressed than the message area displays '97 a'.
CTRL + A yields '1 CTRL-A'. CTRL + 4 yeilds '2100 CTRL-4' and CTRL+SHIFT+ 4 gives '52 4'. What is incorrect in this test?

#1304 Updated by Hynek Cihlar over 8 years ago

Constantin Asofiei wrote:

Hynek Cihlar wrote:

Constantin Asofiei wrote:

Followup for notes 1294/1295/1298: the scrollbar drawing is being performed twice, once from BorderedPanelGuiImpl and once from ScrollPaneGuiImpl.drawScrollBars. I think the drawing call from ScrollPaneGuiImpl.drawScrollBars is not needed, but the layout performed there is needed.

Also:
  1. the scrollbar width (for vertical) and height (for horizontal) looks wrong: we compute both as font-height (in pixels), but on windev01 looks like is 16px width/height.

On Windows/classic theme the scrollbar width (for vertical) and height (for horizontal) is taken from the theme settings.

  1. another issue I don't like is button press on scrollbar - it doesn't look right.

Do you mean the pressed button being drawn only partially? I noticed this after the merge of 2424c/2424d into 2811q.

No, I've fixed the partially drawn issue, is something else. Some positioning (or maybe even clipping rectangle?) not set properly.

Any improvements with revision 10943.

#1305 Updated by Constantin Asofiei over 8 years ago

1811r revision 10944 contains misc fixes related to scroll button clip + the frame detach issue from #2716.

An important note here: physicalDimension(), dimension(), width(), height() and bounds() all need to return the same dimension values. Is not good practice to override physicalDimension() or dimension() - instead override width() and height() - otherwise the clipping rectangles for paint events and anything else which relies on these will not be in sync.

Any improvements with revision 10943.

No, the "down" scroll button is drawing incorrectly. Also, about their size for the scroll buttons: is this really dependent on the DEFAULT-FONT? If you take a look at 10944, you will see the buttons for a vertical scrollbar are not a square, they are "shrinked" horizontally, as the bar's width is forced to font-heigh, not 16px.

#1306 Updated by Hynek Cihlar over 8 years ago

Constantin Asofiei wrote:

Hynek, there is an inconsistency in scrollbar drawing and parenting; the drawing is done using this call chain:
[...]
But the parent chain for the scrollbar is: DropDownGuiImpl -> ScrollPaneGuiImpl -> BorderedPanelGuiImpl -> ScrollBarGuiImpl. This results in a one-pixel shift during drawing. Is this what you are referring in note 1276?

How did you get the trace? I am getting the following draw order.

Inside drop down: DropDownGuiImpl hashcode: 1961501712
Inside drop down: ScrollPaneGuiImpl hashcode: 1991371192
Inside drop down: BorderedPanelGuiImpl hashcode: 658532887
Inside drop down: Viewport hashcode: 402009651
Inside drop down: ScrollableSelectionListGuiImpl hashcode: 45023307
Inside drop down: ScrollBarGuiImpl hashcode: 613298587
Inside drop down: ScrollBarGuiButton hashcode: 1561063579
Inside drop down: ScrollBarGuiButton hashcode: 2034182655
Inside drop down: ScrollBarGuiImpl hashcode: 613298587
Inside drop down: ScrollBarGuiButton hashcode: 1561063579
Inside drop down: ScrollBarGuiButton hashcode: 2034182655

Only drop down child widgets are printed. They seem to be drawn in the correct order.

#1307 Updated by Constantin Asofiei over 8 years ago

Hynek Cihlar wrote:

Constantin Asofiei wrote:

Hynek, there is an inconsistency in scrollbar drawing and parenting; the drawing is done using this call chain:
[...]
But the parent chain for the scrollbar is: DropDownGuiImpl -> ScrollPaneGuiImpl -> BorderedPanelGuiImpl -> ScrollBarGuiImpl. This results in a one-pixel shift during drawing. Is this what you are referring in note 1276?

How did you get the trace? I am getting the following draw order.

[...]

Only drop down child widgets are printed. They seem to be drawn in the correct order.

The drawing was being performed twice, once from ScrollPaneGuiImpl and once from BorderedPanelGuiImpl. In 10944 I've removed the drawing from ScrollPaneGuiImpl.

#1308 Updated by Hynek Cihlar over 8 years ago

Constantin Asofiei wrote:

1811r revision 10944 contains misc fixes related to scroll button clip + the frame detach issue from #2716.

An important note here: physicalDimension(), dimension(), width(), height() and bounds() all need to return the same dimension values. Is not good practice to override physicalDimension() or dimension() - instead override width() and height() - otherwise the clipping rectangles for paint events and anything else which relies on these will not be in sync.

Any improvements with revision 10943.

No, the "down" scroll button is drawing incorrectly. Also, about their size for the scroll buttons: is this really dependent on the DEFAULT-FONT? If you take a look at 10944, you will see the buttons for a vertical scrollbar are not a square, they are "shrinked" horizontally, as the bar's width is forced to font-heigh, not 16px.

Yes, I think the button size should be externalized and based on a theme settings constant value. I didn't attempt to fix this yet.

#1309 Updated by Constantin Asofiei over 8 years ago

Sergey Ivanovskiy wrote:

From Constantin:

Now the web client for this combination CTRL + SHIFT + '4' produces 36($ for US keyboard) as SHIFT + '4'. Should it be 2048 + 512 + 52 = 2612?

Yes, CTR+SHIFT+'4' produces 2612 code in 4GL. Use this testcase on windev01 and "on screen keyboard":
ON ANY-KEY ANYWHERE
DO:
MESSAGE LAST-EVENT:CODE LAST-EVENT:LABEL.
RETURN.
END.

DEF VAR ch AS CHAR.
UPDATE ch WITH FRAME f1.

Currently I have tried this program on windev01 and found the unexpected behaviour if CTRL+SHIFT+A is pressed than the message area displays '97 a'.
CTRL + A yields '1 CTRL-A'. CTRL + 4 yeilds '2100 CTRL-4' and CTRL+SHIFT+ 4 gives '52 4'. What is incorrect in this test?

Are you using the on-screen keyboard on windev01?

#1310 Updated by Constantin Asofiei over 8 years ago

1811r rev 10945 contains the fix for an artifact seen when closing the combo's drop-down: when clearing the content, BorderedPanelGuiImpl needs to include the border, not exclude it - as BorderPanelGuiImpl.location() returns it's location translated with the border insets (for child placement/drawing), the insets had to be excluded explicitly.

#1311 Updated by Sergey Ivanovskiy over 8 years ago

Constantin Asofiei wrote:

Are you using the on-screen keyboard on windev01?

No, missed this important detail, checked CTRL+SHIFT+4 produces this message "2612 ". test.html helps to find that web client handles CTRL+SHIFT sequences incorrectly, working on this and then to follow #1284.

#1312 Updated by Hynek Cihlar over 8 years ago

The checked in revision 10946 fixes the layout and draw issues which involved insets. Also scrollbar size doesn't depend on a font size anymore. The artifacts and border shifts are gone, scrollbars are drawn properly.

Constantin, this also touches some of your fixes. Please review.

#1313 Updated by Hynek Cihlar over 8 years ago

Hynek Cihlar wrote:

The checked in revision 10946 fixes the layout and draw issues which involved insets. Also scrollbar size doesn't depend on a font size anymore. The artifacts and border shifts are gone, scrollbars are drawn properly.

Constantin, this also touches some of your fixes. Please review.

I should also mention that I only tested the swing client.

#1314 Updated by Sergey Ivanovskiy over 8 years ago

Fixed CTRL+SHIFT. Committed revision 10947.

#1315 Updated by Sergey Ivanovskiy over 8 years ago

Greg, I would like to commit the keystroke JS test to testcases/uast/keyboards for the web client in order to track its changes. This test is very helpful and is tested. Proposed to add test.html, keystrokes.js and keyboard_testcases.js. In order to run this test it needs to place all these files with the target tested web files p2j.keyboard.js and p2j.keymap.js.

#1316 Updated by Greg Shah over 8 years ago

Sergey Ivanovskiy wrote:

Greg, I would like to commit the keystroke JS test to testcases/uast/keyboards for the web client in order to track its changes. This test is very helpful and is tested. Proposed to add test.html, keystrokes.js and keyboard_testcases.js. In order to run this test it needs to place all these files with the target tested web files p2j.keyboard.js and p2j.keymap.js.

Agreed, do it. Please put in a readme.txt that explains how to set it up and run it.

#1317 Updated by Greg Shah over 8 years ago

On 09/28/2015 03:41 PM, Serg Ivanovskiy wrote:

Greg,

Now, I am working on #1811-1284

The template console program is here, it needs only to add target keystrokes and then planning to run it on windev01
#include <windows.h>

void SetNumLock( BOOL bState ) {
BYTE keyState256;

GetKeyboardState((LPBYTE)&keyState);
if( (bState && !(keyState[VK_NUMLOCK] & 1)) ||
(!bState && (keyState[VK_NUMLOCK] & 1)) ) {
// Simulate a key press
keybd_event( VK_NUMLOCK,
0x45,
KEYEVENTF_EXTENDEDKEY | 0,
0 );

// Simulate a key release
keybd_event( VK_NUMLOCK,
0x45,
KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP,
0);
}
}

void main() {
SetNumLock( TRUE );
}

and run it from progress with help of

procedure WinExec External "kernel32.dll":
define input parameter ProgramName as character.
define input parameter Presentation as long.
end procedure
run WinExec ( input "pathToKeystokesGenerator", input 2).

No, this is not how you would do it.

Broadly speaking, there are 2 kinds of native WIN32 executable: EXE and DLL.

Anything with a main() is going to generate an EXE. When you run an EXE you get a separate process. I'm not sure that it will route the injected keys to another process. You should test that. Be prepared to need extra code to get that separate process to be able to send keys to the running 4GL program. It can be done, but I think you would have to have the window handle for the correct widget in the 4GL program and then you would use something like SendInput() or PostThreadMessage() to do this. See:

http://stackoverflow.com/questions/12099957/how-to-send-a-keystroke-to-an-other-process-ex-a-notepad

To execute this from the 4GL program, you would simply use the OS-COMMAND language statement to create a child process (e.g. to run the EXE).

HOWEVER, I think it is easier to create a DLL. You can pattern it off of the /uast/library_calls/testapi/ "project". Use the makefile and create your own version of this that is cut down. You only need to export a single API. You can use dllexport in the code to mark the API as something to export and should not need to use the testapi.def (that is only for exporting by ordinal). You will only need 1 .c file for the code itself. There is no main(). If you have issues, post them here and I will help resolve you.

Once you have this in a DLL form, then you can call the exported function directly from the 4GL. If your exported API is called sendKeystroke() and it is in a DLL called keystroke_feeder.dll, then you can define it using:

PROCEDURE "sendKeystroke" EXTERNAL "keystroke_feeder.dll":
...parm defs...
END.

RUN sendKeystroke (...parms...).

This will allow you to execute that function within the context of the 4GL process since it is just calling the code, it is not creating a separate process. Using this approach avoids the "how do I route the keys" issue, because the keys should naturally route to the current process.

Please create a separate subdirectory under testcases/uast/keyboards/ for this sub-project (both the 4GL code and the C code/build script). Make sure to create a readme.txt to describe how to build this, install it (usually this means copying it to the c:\windows\system32\ directory) and run it.

What is the most priority task I should do?

Sergey.

Your top priority is to get the key processing to be correct. If there are fixes needed for the Swing client, please make those as well. Both Swing GUI and Web GUI should process keys the same way.

#1318 Updated by Sergey Ivanovskiy over 8 years ago

Agreed, do it. Please put in a readme.txt that explains how to set it up and run it.

Committed revision 1356 for the testcases project adds web clients keyboard test.

added uast/keyboards/web
added uast/keyboards/web/keyboard_testcases.js                                 
added uast/keyboards/web/keystrokes.js
added uast/keyboards/web/readme.txt
added uast/keyboards/web/test.html

#1319 Updated by Constantin Asofiei over 8 years ago

Branch 1811r rev 10947 10948 contains a refactoring on how the GUI driver is accessed: to allow concurrent usage, this is done in an exclusive way. Each Thread requiring work with the GUI driver will obtain exclusive access to it via GuiDriver.selectWindow and will release it via GuiDriver.releaseWindow. Scoped usage is possible, so the actual release will be performed only when the outermost scope is exited.

Note that I've added two new APIs in AbstractWidget: getText[Width/Height] and getText[Width/Height]Native. The difference between them is that the first version calls FontManager.getText[Width/Height] and the native version calls the GuiDriver APIs directly. These are needed because accessing the GuiDriver APIs requires selection of a window (and establishing exclusive access) before interrogating them - these ensures a window is selected always, regardless of the context from which these are called.

Note that the ALERT-BOX runtime is broken currently (I think is broken in trunk, too): the main problem is that AbstractWidget.window() is used throughout the code to resolve the widget's Window instance, but an AlertBoxGuiImpl is NOT a Window, but a ModalWindow...

#1320 Updated by Constantin Asofiei over 8 years ago

In note 1319 I meant branch 1811r rev 10948, not 10947.

#1321 Updated by Hynek Cihlar over 8 years ago

Constantin Asofiei wrote:

Note that the ALERT-BOX runtime is broken currently (I think is broken in trunk, too): the main problem is that AbstractWidget.window() is used throughout the code to resolve the widget's Window instance, but

an AlertBoxGuiImpl is NOT a Window, but a ModalWindow...

I will resolve this in the scope of #2560.

#1322 Updated by Constantin Asofiei over 8 years ago

Hynek Cihlar wrote:

Constantin Asofiei wrote:

Note that the ALERT-BOX runtime is broken currently (I think is broken in trunk, too): the main problem is that AbstractWidget.window() is used throughout the code to resolve the widget's Window instance, but

an AlertBoxGuiImpl is NOT a Window, but a ModalWindow...

I will resolve this in the scope of #2560.

OK. We should add an ALERT-BOX test to the standard set of GUI tests for regression testing.

Something else about 10948 which I forgot to mention: it fixes the 2. EWS.resize() deadlocks (CA) from note 1087. The Web client has a dedicated thread to process all repainting requests; window maximize/restore no longer abends for the Web client, now, and it works.

#1323 Updated by Sergey Ivanovskiy over 8 years ago

HOWEVER, I think it is easier to create a DLL. You can pattern it off of the /uast/library_calls/testapi/ "project". Use the makefile and create your own version of this that is cut down. You only need to export a single API. You can use dllexport in the code to mark the API as something to export and should not need to use the testapi.def (that is only for exporting by ordinal). You will only need 1 .c file for the code itself. There is no main(). If you have issues, post them here and I will help resolve you.
... install it (usually this means copying it to the c:\windows\system32\ directory) and run it.

Greg, what are paths the Progress uses to find the external dll referenced in the procedure statement? The tested function from dll works if the dll is placed in the progress executable directory that is the current directory for C:\dlc91d\bin\prowin32.exe. If the custom dll is placed in the windows systems directory or the current executable directory, the Progress loads it successfully.

#1324 Updated by Greg Shah over 8 years ago

The rules are based on the platform on which Progress is running. For Windows:

https://msdn.microsoft.com/en-us/library/7d83bc18.aspx

#1325 Updated by Sergey Ivanovskiy over 8 years ago

The rules are based on the platform on which Progress is running. For Windows:

https://msdn.microsoft.com/en-us/library/7d83bc18.aspx

5) The directories listed in the PATH environment variable.
It seems that this rule doesn't work for Progress, because I tried to add the directory with dll to the PATH variable and restarting prowin32.exe couldn't help, but not sure that it was fair test.

#1326 Updated by Hynek Cihlar over 8 years ago

Sergey Ivanovskiy wrote:

The rules are based on the platform on which Progress is running. For Windows:

https://msdn.microsoft.com/en-us/library/7d83bc18.aspx

5) The directories listed in the PATH environment variable.
It seems that this rule doesn't work for Progress, because I tried to add the directory with dll to the PATH variable and restarting prowin32.exe couldn't help, but not sure that it was fair test.

Did you try to log out? The parent process most likely didn't refresh its environment.

#1327 Updated by Sergey Ivanovskiy over 8 years ago

Did you try to log out? The parent process most likely didn't refresh its environment.

Checked the PATH variable, updating the PATH to add dll directory works properly, it helps.

#1328 Updated by Constantin Asofiei over 8 years ago

Hynek, there is a regression in ChUI in 1811r, which can be seen in the RADIO-SET widget (but is not related to RADIO-SET):

def var ch as char format "x(10)".

update ch
    view-as radio-set
    radio-buttons
      "foo",       "f", 
      "bar",       "b", 
      "test2",     "t", 
      "something", "s", 
      "else",      "e" 
    at 7
    with frame f1 row 4 width 25 centered no-labels.

message ch.

This uses a boxed frame. When selecting another button, the first button is not re-drawn. The widget hierarchy for the first foo button looks like this, with the x:y location relative to parent:

FrameChuiImpl 27:3
ScrollPaneImpl 0:0
BorderedPanelImpl 0:0
Viewport 1:1
SensitiveScrollContainer 0:0
RadioSet 6:0

When AbstractWidget.repaint is called for foo RadioSet, it needs to find the absolute location. It goes up the hierarchy, and when it reaches the BorderedPanelImpl, it sees its insets as (1,1,1,1) and translates them one more time... the problem is Viewport is already located at (1,1), so the BorderedPanelImpl should no translate with the insets (or not even have them at all).

Do you think is safe to override BorderedPanelImpl.insets to return the insets directly?

#1329 Updated by Hynek Cihlar over 8 years ago

Constantin, I am suspecting the viewport to be at the wrong location, I think it should be positioned at 0,0. Let me check the layout logic of the radio set widget.

#1330 Updated by Hynek Cihlar over 8 years ago

The problem was with insets being not properly translated in ChUI when bordered panels were involved. Constantin, see 1811r revision 10950.

#1331 Updated by Constantin Asofiei over 8 years ago

Hynek Cihlar wrote:

The problem was with insets being not properly translated in ChUI when bordered panels were involved. Constantin, see 1811r revision 10950.

Unfortunately this makes things worse... from the MAJIC main menu, press V/F - the Find frame does not display the fill-in's value, "0". I don't have a separate test for this yet.

#1332 Updated by Hynek Cihlar over 8 years ago

Constantin Asofiei wrote:

Hynek Cihlar wrote:

The problem was with insets being not properly translated in ChUI when bordered panels were involved. Constantin, see 1811r revision 10950.

Unfortunately this makes things worse... from the MAJIC main menu, press V/F - the Find frame does not display the fill-in's value, "0". I don't have a separate test for this yet.

Are you planning to create the separate test? If not I will do myself.

#1333 Updated by Constantin Asofiei over 8 years ago

Hynek Cihlar wrote:

Constantin Asofiei wrote:

Hynek Cihlar wrote:

The problem was with insets being not properly translated in ChUI when bordered panels were involved. Constantin, see 1811r revision 10950.

Unfortunately this makes things worse... from the MAJIC main menu, press V/F - the Find frame does not display the fill-in's value, "0". I don't have a separate test for this yet.

Are you planning to create the separate test? If not I will do myself.

Please go ahead, I'm not working on it.

#1334 Updated by Sergey Ivanovskiy over 8 years ago

Constantin Asofiei wrote:

Unfortunately this makes things worse... from the MAJIC main menu, press V/F - the Find frame does not display the fill-in's value, "0". I don't have a separate test for this yet.

Is it related to keypress issue? What is V/F combinations?

#1335 Updated by Constantin Asofiei over 8 years ago

Sergey Ivanovskiy wrote:

Constantin Asofiei wrote:

Unfortunately this makes things worse... from the MAJIC main menu, press V/F - the Find frame does not display the fill-in's value, "0". I don't have a separate test for this yet.

Is it related to keypress issue? What is V/F combinations?

No, is not related to keypress. My note is related to a regression in the MAJIC tests, in the ChUI code.

#1336 Updated by Hynek Cihlar over 8 years ago

Constantin Asofiei wrote:

Please go ahead, I'm not working on it.

Constantin, I have compared 1811r revision 10950 and 10949 and didn't find any functional difference of V/F. I am probably missing something. Also I don't have an up to date converted Majic source so I connected to the server running under your account.

#1337 Updated by Sergey Ivanovskiy over 8 years ago

Greg, could you help what is incorrect in the test dll. The project is committed to uast/keyboards/keystrokes_feederapi and makefile is provided.(Committed revision 1368.) The Progress could't load the dll procedure even though the target dll is added to the path. But the test program loads and calls it successfully. At the beginning I tested a simple dll that exports a function without parameters and the Progress loaded and performed it successfully. There are screenshots from windev01.

#1338 Updated by Greg Shah over 8 years ago

Have you tried absolute pathing ("c:~\Users~\sbi~\keystrokesfeeder.dll") in the PROCEDURE EXTERNAL?

If you build as 32-bit CDECL will be used, so add CDECL to the PROCEDURE EXTERNAL. But that is not the load issue, it would be an issue when you execute runTest. The CDECL will be ignored in 64-bit mode, so that is OK.

#1339 Updated by Sergey Ivanovskiy over 8 years ago

Have you tried absolute pathing ("c:~\Users~\sbi~\keystrokesfeeder.dll") in the PROCEDURE EXTERNAL?

If you build as 32-bit CDECL will be used, so add CDECL to the PROCEDURE EXTERNAL. But that is not the load issue, it would be an issue when you execute runTest. The CDECL will be ignored in 64-bit mode, so that is OK.

Greg, it seems the problem is due to mingw on windev01 is 64 bit, but if "keystrokesfeeder.dll" is build on my win32 - mingw than all is okay,
another reason could be a dll export specification should be __declspec(dllexport) or __attribute__ ((dllexport))
Planning to investigate it and then to complete scancodes.txt file and to add new keystokes simulated function to deliver key events to the given window.

#1340 Updated by Sergey Ivanovskiy over 8 years ago

Constantin, Window.java uses java 8 compiler feature to determine if local variables are final.

#1341 Updated by Constantin Asofiei over 8 years ago

Sergey Ivanovskiy wrote:

Constantin, Window.java uses java 8 compiler feature to determine if local variables are final.

Please be more specific: are you talking about existing code, new code added by you? If so, what part?

#1342 Updated by Sergey Ivanovskiy over 8 years ago

Please be more specific: are you talking about existing code, new code added by you? If so, what part?

In java 7 local variables cfg, windId, mgr that used in local classes must be defined as final, but for java 8 it need not to mark such local variables as final, this feature surprises me.

   public static void pushConfig(WindowConfig cfg)
   {
      WidgetId windId = cfg.id;
      ConfigManager mgr = ConfigManager.getInstance();
      ThinClient tc = ThinClient.getInstance();

      if (windId.equals(WidgetId.DEFAULT_WINDOW_ID))
      {
         // update default widow  
         tc.eventDrawingBracket(getDefaultWindow(), false, true, new Runnable()
         {
            public void run()
            {
               mgr.replaceConfig(getDefaultWindow().config(), cfg);
            }
         });
      }

#1343 Updated by Constantin Asofiei over 8 years ago

Sergey Ivanovskiy wrote:

Please be more specific: are you talking about existing code, new code added by you? If so, what part?

In java 7 local variables cfg, windId, mgr that used in local classes must be defined as final, but for java 8 it need not to mark such local variables as final, this feature surprises me.
[...]

Ah, this is the effectively final feature in Java 8 - if the local var is assigned only once and used in Anonymous inner classes and/or lambda expressions, it automatically makes the var final. Otherwise, if the var is assigned more than once, then it fails at compilation.

Until now I was relying on the compiler notifying me the var needs to be made final :)

#1344 Updated by Sergey Ivanovskiy over 8 years ago

Playing with keystrokesfeeder.dll on windev01 helps to find an interesting Progress 4GL combinations CTRL + [ that produces codes 2267 and 27 (ESC) with "Procedure complete Press the space bar to continue." Also CTRL + a CTRL + A CTRL + SHIFT + a = 1,
CTRL+z CTRL +Z CTRL +SHIFT + z = 26 are the same, thus letter is a capital for CTRL + letter. Committed revision 10951 implements this logic.

#1345 Updated by Constantin Asofiei over 8 years ago

1811r rev 10952 contains changes for #2716 related to multi-window focus and frame placement.

From note 1087:
2. EWS.resize() deadlocks (CA) - this one is fixed
3. test/fix window resize in Web (CA) - I'm leaving this until after the font issues are fixed. My concern in GUI is that window resizing triggers full window redraw... and I don't think this will be fast. I'm thinking on drawing an "outer" rectangle (without redrawing) which shows how the new resized window would look and redraw only when the mouse dragging is finished.

The remaining issues related to fonts are: (I'm taking these next)
4. font anti-aliasing (CA)
5. frame title looks less bold, probably due to the anti-aliasing (CA)
6. text positioning is slightly above and to the right compared to Swing client (CA)
7. web client support for font aliases (CA) - font aliases work the same in both GUI and Swing; the only issue remaining is related to 1794 - configure the directory with fonts which mimic the look&feel of the bitmap (unsupported) fonts: System, MS Sans Serif (I don't think this one is used, is not mentioned in #2322) and FixedSys. Also, we need to configure the .ttf files for Microsoft Sans Serif and Courier New, with bold and normal versions.
8. web client support for underline fonts (CA)

#1346 Updated by Sergey Ivanovskiy over 8 years ago

Finished keystrokes simulator for windows committed revision 1374 (testcases). (The open issue is how to build this dll with mingw64 to be loaded by Progress 4Gl.)

#1347 Updated by Constantin Asofiei over 8 years ago

1811r rev 10953 contains a fix for web client plus a combo-box fix (the widgets need to be de-registered from the window BEFORE the widget gets destroyed...)

#1348 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811r Revisions 10952/10953

Wow, this must fix a lot of problems! Our multi-window support seems much more complete now. I especially like the move of code to Window and the reduced use of static methods in many places.

I don't see any obvious problems. I suspect that merging with the coming changes in #2560 will be a bit tricky, but otherwise it seems good.

#1349 Updated by Greg Shah over 8 years ago

I'm thinking on drawing an "outer" rectangle (without redrawing) which shows how the new resized window would look and redraw only when the mouse dragging is finished.

Agreed. It is a reasonable choice.

#1350 Updated by Sergey Ivanovskiy over 8 years ago

Found that CoordinatesConversion uses BigDecimal.valueof(double) instead of new BigDecimal(double). What is the reason to prefer BigDecimal.valueof(double)?

#1351 Updated by Hynek Cihlar over 8 years ago

Sergey Ivanovskiy wrote:

Found that CoordinatesConversion uses BigDecimal.valueof(double) instead of new BigDecimal(double). What is the reason to prefer BigDecimal.valueof(double)?

The reason is that the method can serve singletons representing common values, like zero.

#1352 Updated by Hynek Cihlar over 8 years ago

Hynek Cihlar wrote:

Sergey Ivanovskiy wrote:

Found that CoordinatesConversion uses BigDecimal.valueof(double) instead of new BigDecimal(double). What is the reason to prefer BigDecimal.valueof(double)?

The reason is that the method can serve singletons representing common values, like zero.

The other reason, but I am not sure whether this is the case with the code you refer to, is that by using valueOf(double) you will end up with the canonical string representation of the value. BigDecimal(double) on the other hand will give you the closest match possible.

So BigDecimal.valueOf(0.1) will create a big decimal holding the exact value of 0.1. But with BigDecimal(double) you will end up with something like 0.10000000000000000555111xxxxx.

#1353 Updated by Greg Shah over 8 years ago

Sergey Ivanovskiy wrote:

Finished keystrokes simulator for windows committed revision 1374 (testcases). (The open issue is how to build this dll with mingw64 to be loaded by Progress 4Gl.)

Please add a readme.txt to that directory. It should explain:

1. How to build it in both 32-bit and 64-bit mode.
2. How to install it in both 32-bit and 64-bit mode.
3. How to run it in both 32-bit and 64-bit mode.
4. What to do with the output.
5. Any limitations or problems. If 64-bit mode just doesn't work right now, then say so.

As long as you have gotten all the output you needed from 32-bit mode, it is fine. The WIN32 event processing is the same for 32-bit and 64-bit mode, so I don't think you need to repeat it.

I assume you did in fact get all the output you needed?

#1354 Updated by Greg Shah over 8 years ago

Sergey: what remaining work is needed for the "make Swing/web keyboard processing compatible" task?

#1355 Updated by Sergey Ivanovskiy over 8 years ago

Sergey: what remaining work is needed for the "make Swing/web keyboard processing compatible" task?

Planning to finish it today. It needs the Swing GUI keystrokes test to check with Progress 4GL keys processing. It can be checked manually by testing one keys combinations from each of these keys sets {CTRL + key}, {SHIFT + key}, {Alt + key}, {CTRL + SHIFT + key}, {CTRL + Alt + key}, {SHIFT + Alt + key}, {CTRL + SHIFT + Alt + key}. For the web client we have the automated GUI keystrokes test. The keystrokes simulator is ready, except readme.txt (will be added today) and the output is not ready for all possible keys combinations, now web keyboard processing uses /testcases/uast/keyboards/win-gui.txt and the output from keystrokes simulator for CTRL + SHIFT + key.

#1356 Updated by Sergey Ivanovskiy over 8 years ago

Committed revision 1375 adds readme.txt and scancode.doc specification to testcases/uast/keyboards/keystrokes_feederapi.

#1357 Updated by Constantin Asofiei over 8 years ago

1811r revision 10954 adds underline font support for Web client (item 8 from note 1087).

I'm working on a repaint regression for the GUI menubar.

#1358 Updated by Constantin Asofiei over 8 years ago

1811r revision 10955 adds a workaround for a MENU-BAR bug introduced by the new clipping approach; the bug is related to the fact that a menubar repaint with expanded sub-menus uses a rectangle which contains all expanded sub-menus, not individual sub-menu bodies...

#1359 Updated by Eugenie Lyzenko over 8 years ago

The code to draw 3D line has been added to the WindowGuiImpl.drawBorder(). The resulting screenshot attached to demonstrate the change.

#1360 Updated by Sergey Ivanovskiy over 8 years ago

The Swing GUI client didn't pass the manual test due to the pressed valid keys combinations were treated as several keys codes instead of one code.
  1. {CTRL + key} -> CTRL + '2 @' produces two codes 2098 and 0,but Progress 4GL - 0.
  2. {SHIFT + key}-> passed
  3. {Alt + key} -> Alt + '2 @' produces 1024 and 1074 should be 1074 and Alt + 'A' produces 1024 and 1089 but should be 1089
  4. {CTRL + SHIFT + key} -> CTRL + SHIFT + 'C' produces CTRL + SHIFT and CTRL +'C' but should be CTRL + 'C' = 3
  5. {CTRL + Alt + key} -> CTRL + ALT + 'S' produces CTRL + ALT = 3072, but should be CTRL + 'S' = 1043
  6. {SHIFT + Alt + key} -> ALT + SHIFT + '2' produces 1024 and 1088 should be 1088
  7. {CTRL + SHIFT + Alt + key} -> CTRL + ALT + SHIFT + 'D' produces 1024, 2064 and 1028 should be ALT + CTRL + 'D' = 1028.

Planning to fix it.

#1361 Updated by Constantin Asofiei over 8 years ago

1811r rev 10956 contains a fix for 5. frame title looks less bold, probably due to the anti-aliasing (CA) - this is caused by setting the bold style for a font which has a custom definition for its bold version - in this case, drawing using bold-style font (which is already "bold" from the .ttf) gets us in incorrect drawing in Web.

The other issue - related to disable anti-aliasing during text drawing - I can't find a solution. I'll postpone this one a little.

#1362 Updated by Greg Shah over 8 years ago

Eugenie Lyzenko wrote:

The code to draw 3D line has been added to the WindowGuiImpl.drawBorder(). The resulting screenshot attached to demonstrate the change.

Please check in your changes as you go. The only restriction is that each check-in should not break the build and hopefully it should not cause regressions.

#1363 Updated by Eugenie Lyzenko over 8 years ago

Greg Shah wrote:

Please check in your changes as you go. The only restriction is that each check-in should not break the build and hopefully it should not cause regressions.

Just to clarify to avoid tree corruption. You mean commit into branch or trunk?

#1364 Updated by Greg Shah over 8 years ago

You mean commit into branch or trunk?

I mean the task branch 1811r. This is what I meant by my notes in #2679, #2680 and #2726.

#1365 Updated by Greg Shah over 8 years ago

You mean commit into branch or trunk?

I mean the task branch 1811r. This is what I meant by my notes in #2679, #2680 and #2726.

#1366 Updated by Eugenie Lyzenko over 8 years ago

Task branch 1811r for review updated to revision 10958.

Fix for window 3D like border in GUI.

#1367 Updated by Constantin Asofiei over 8 years ago

Hynek Cihlar wrote:

Constantin Asofiei wrote:

Please go ahead, I'm not working on it.

Constantin, I have compared 1811r revision 10950 and 10949 and didn't find any functional difference of V/F. I am probably missing something.

I'm trying to extract a testcase. The problem is that the search-field in the frame shown after F is pressed should display a 0 (and underlined, as it is editable), but is not doing so with 1811r (the fill-in doesn't get repainted).

Also I don't have an up to date converted Majic source so I connected to the server running under your account.

You can take the built MAJIC jars from the devsrv01:/tmp/majic.cvt/ jars, if you want to avoid re-converting your MAJIC.

#1368 Updated by Hynek Cihlar over 8 years ago

Constantin Asofiei wrote:

I'm trying to extract a testcase. The problem is that the search-field in the frame shown after F is pressed should display a 0 (and underlined, as it is editable), but is not doing so with 1811r (the fill-in doesn't get repainted).

The zeros aren't displayed in the trunk either. This must have been broken for some time already.

Also I don't have an up to date converted Majic source so I connected to the server running under your account.

You can take the built MAJIC jars from the devsrv01:/tmp/majic.cvt/ jars, if you want to avoid re-converting your MAJIC.

Thanks, I already have a fresh conversion.

#1369 Updated by Greg Shah over 8 years ago

Rebased task branch 1811r from P2J trunk revision 10944. The current revision in 1811r is now 10963.

#1370 Updated by Greg Shah over 8 years ago

Sergey: Please provide a list of the open work for the key processing improvements. In addition to note 1360 you've previously mentioned something about key echoing. I also need to know how much time is estimated to be needed to resolve those items.

Constantin: Which of your items currently listed in note 1087 for 1811r do you expect to be able to finish by end of day tomorrow? Today, i've made edits to that note, so you should make sure to read it again.

#1371 Updated by Constantin Asofiei over 8 years ago

1811r rev 10964:
  • Fixed a regression in ChUI (events when no focus exists are sent to ACTIVE-WINDOW)
  • fixed a NPE when closing the drop-down by clicking on the COMBO-BOX widget
  • some improvements in drawing the labels for BUTTON and the editable text for FILL-IN (so the Web and Swing can draw almost the same) - #1087 note 6. text positioning is slightly above and to the right compared to Swing client (CA). Button is drawn OK, but FILL-IN editable part is drawn some pixels higher in Web.

Also, after rebase there are regressions in GUI drawing (widgets are miss-placed, dimensions are computed not correct...)

Constantin: Which of your items currently listed in note 1087 for 1811r do you expect to be able to finish by end of day tomorrow?

3. test/fix window resize in Web (CA)
I haven't started working on it... I will focus on fixing the regressions in 1811r related to GUI and ChUI.

4. font anti-aliasing (CA)
I can't find a way to disable anti-aliasing when drawing text, via some simple setting in CSS/canvas context. What I've found is manually adjusting the drawn text via some alpha-blending: http://stackoverflow.com/questions/5262174/poor-anti-aliasing-of-text-drawn-on-canvas but this seems over-complicated and might slow down the drawing even more.

7. web client support for font aliases (CA)
The issue here is finding font replacements for FixedSys and System. I think we should alias these fonts to the same fonts they used as replacements, in the "pop" windows displayed in the Applet - as Java doesn't support bitmap fonts, they must have used some other explicit fonts to replace these. Actually, it would be good to know if these fonts are actually used for drawing. Note that this is a directory configuration issue. Beside this, I'll finish the remaining alias support in P2J today.

#1372 Updated by Eugenie Lyzenko over 8 years ago

Fix for status line issue is ready. The fixed screenshot attached here. Preparing to commit in task branch.

#1373 Updated by Eugenie Lyzenko over 8 years ago

Task branch 1811r for review updated to revision 10965.

Fix for status line drawing issues.

#1374 Updated by Constantin Asofiei over 8 years ago

1811r rev 10966 fixes font alias support for Web client.

On a side note: the regression in GUI is only for EDITOR, the other widgets look fine.

#1375 Updated by Greg Shah over 8 years ago

3. test/fix window resize in Web (CA)
I haven't started working on it... I will focus on fixing the regressions in 1811r related to GUI and ChUI.

OK. Work on this next, after the regressions. If it can't be done it time, we'll defer it, but if it can be done we will include it.

4. font anti-aliasing (CA)

I'm going to defer this one. Let's get the fonts matched up as best we can and see what the result is like. I also want to finish the #1794 remaining items first.

The issue here is finding font replacements for FixedSys and System. I think we should alias these fonts to the same fonts they used as replacements, in the "pop" windows displayed in the Applet - as Java doesn't support bitmap fonts, they must have used some other explicit fonts to replace these. Actually, it would be good to know if these fonts are actually used for drawing. Note that this is a directory configuration issue.

This one can be next after the resize. I guess it goes with the remaining #1794 items? I will plan for it to be in 1811s.

Please open a discussion with Paul Eames regarding the fonts used in the applet and if they are used for drawing. He'll probably have to ask one of BPM developers, but they should be able to answer your questions.

#1376 Updated by Greg Shah over 8 years ago

On a side note: the regression in GUI is only for EDITOR, the other widgets look fine.

Good. BTW, have you looked at #2737?

#1377 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

The issue here is finding font replacements for FixedSys and System. I think we should alias these fonts to the same fonts they used as replacements, in the "pop" windows displayed in the Applet - as Java doesn't support bitmap fonts, they must have used some other explicit fonts to replace these. Actually, it would be good to know if these fonts are actually used for drawing. Note that this is a directory configuration issue.

I guess it goes with the remaining #1794 items?

What remaining issues do you have on the list for #1794? Beside font alises/custom font support in runtime P2J (which are already working), there is nothing else on my list.

Please open a discussion with Paul Eames regarding the fonts used in the applet and if they are used for drawing. He'll probably have to ask one of BPM developers, but they should be able to answer your questions.

I'll use #2322 for this.

BTW, have you looked at #2737?

No, I'll look at it now.

#1378 Updated by Constantin Asofiei over 8 years ago

1811r rev 10967 fixes a regression in GUI EDITOR drawing plus the EDITOR text selection issue in #2737

#1379 Updated by Greg Shah over 8 years ago

What remaining issues do you have on the list for #1794? Beside font alises/custom font support in runtime P2J (which are already working), there is nothing else on my list.

I guess it is just the configuration/setup of the proper matching for the bitmap fonts (and any non-bitmap fonts which are not matching well).

I assume everything else from task 1794 note 24 is already resolved?

#1380 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

What remaining issues do you have on the list for #1794? Beside font alises/custom font support in runtime P2J (which are already working), there is nothing else on my list.

I guess it is just the configuration/setup of the proper matching for the bitmap fonts (and any non-bitmap fonts which are not matching well).

I assume everything else from task 1794 note 24 is already resolved?

All except this one: changing the font-table via the SYSTEM-DIALOG FONT and PUT KEY VALUE FONT (FontTable.chooseFont, saveFont and saveFontTable APIs).

#1381 Updated by Sergey Ivanovskiy over 8 years ago

Sergey: Please provide a list of the open work for the key processing improvements. In addition to note 1360 you've previously mentioned something about key echoing. I also need to know how much time is estimated to be needed to resolve those items.

Greg, please review the committed revision 10968. It should fix #1360. There are no open tasks for the key processing. If related bugs will be found it can be fixed ad hoc (a hotfix).
By key echoing I mean the delay time from keys are pressed down and up on the keyboard following by their appearance in the editor view.
I am not sure if it is due to the strong synchronization on one lock object to read from the websocket and to write to the websocket. It needs to investigate.

#1382 Updated by Greg Shah over 8 years ago

All except this one: changing the font-table via the SYSTEM-DIALOG FONT and PUT KEY VALUE FONT (FontTable.chooseFont, saveFont and saveFontTable APIs).

We will defer this one, see #2747.

#1383 Updated by Greg Shah over 8 years ago

By key echoing I mean the delay time from keys are pressed down and up on the keyboard following by their appearance in the editor view.
I am not sure if it is due to the strong synchronization on one lock object to read from the websocket and to write to the websocket. It needs to investigate.

OK, go ahead and look into this. I have added it to branch 1811s (see note 1087) but if you come up with fixes before end of day tomorrow, we can easily include them in 1811r.

After this key processing lag fix, your next work will be about clipboard support in GUI. See #1811-722 and #1811-723

#1384 Updated by Constantin Asofiei over 8 years ago

Hynek Cihlar wrote:

Constantin Asofiei wrote:

Please go ahead, I'm not working on it.

Constantin, I have compared 1811r revision 10950 and 10949 and didn't find any functional difference of V/F. I am probably missing something. Also I don't have an up to date converted Majic source so I connected to the server running under your account.

This is a real regression, as is not present in trunk. To duplicate, use frame fo activated via v in this test:

def var ch as char.
def var i as int.

form ch view-as editor size 20 by 20 with frame f1 side-labels.
form i label "something" with frame fo side-labels overlay at col 2 row 2.

enable all with frame f1.

on 'v' anywhere do:
  update i with frame fo.
end.

wait-for go of frame f1.

I'll pick it up tomorrow morning.

#1385 Updated by Eugenie Lyzenko over 8 years ago

Task branch 1811r for review updated to revision 10969.

Fix for status line one pixel issue.

#1386 Updated by Eugenie Lyzenko over 8 years ago

Some recent 1811r changes cause the GUI regression in horizontal radio-set. NPE in AbstractGuiDriver, line 2525:

...
   private TextMetrics resolveTextMetrics(String text, FontDetails<F> fd)
   {
      TextKey<F> key = new TextKey(text, fd.font);

      TextMetrics metrics = textMetricsCache.get(key);
      if (metrics == null)
      {
         FontMetricsHelper fm = ews.getFontMetrics(fd); <-- problematic call
         int width = fm.stringWidth(text);
         int height = ews.getTextHeight(text, fd);

         metrics = new TextMetrics(width, height);

         textMetricsCache.put(key, metrics);
      }

      return metrics;
   }
...

The problem is ews == null This is called from AbstractGuiDriver.getTextWidth(). The issue log:
...
java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:497)
        at com.goldencode.p2j.util.Utils.invoke(Utils.java:1285)
        at com.goldencode.p2j.main.StandardServer$MainInvoker.execute(StandardServer.java:1767)
        at com.goldencode.p2j.main.StandardServer.invoke(StandardServer.java:1270)
        at com.goldencode.p2j.main.StandardServer.standardEntry(StandardServer.java:427)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:497)
        at com.goldencode.p2j.util.MethodInvoker.invoke(MethodInvoker.java:76)
        at com.goldencode.p2j.net.Dispatcher.processInbound(Dispatcher.java:705)
        at com.goldencode.p2j.net.Conversation.block(Conversation.java:319)
        at com.goldencode.p2j.net.Conversation.run(Conversation.java:163)
        at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.NullPointerException
        at com.goldencode.p2j.ui.client.gui.driver.AbstractGuiDriver.resolveTextMetrics(AbstractGuiDriver.java:2525)
        at com.goldencode.p2j.ui.client.gui.driver.AbstractGuiDriver.getTextWidth(AbstractGuiDriver.java:1244)
        at com.goldencode.p2j.ui.client.gui.RadioButtonGuiImpl.width(RadioButtonGuiImpl.java:246)
        at com.goldencode.p2j.ui.client.RadioSet.refreshItems(RadioSet.java:515)
        at com.goldencode.p2j.ui.client.RadioSet.afterConfigUpdate(RadioSet.java:695)
        at com.goldencode.p2j.ui.client.RadioSet.afterConfigUpdate(RadioSet.java:147)
        at com.goldencode.p2j.ui.ConfigSyncManager.markScopeEnd(ConfigSyncManager.java:247)
        at com.goldencode.p2j.ui.ConfigManager.syncConfigChanges(ConfigManager.java:497)
        at com.goldencode.p2j.ui.ConfigManager.replaceConfig(ConfigManager.java:365)
        at com.goldencode.p2j.ui.client.RadioSet.initialize(RadioSet.java:190)
        at com.goldencode.p2j.ui.client.gui.RadioSetGuiImpl.initialize(RadioSetGuiImpl.java:103)
        at com.goldencode.p2j.ui.client.gui.RadioSetGuiImpl.initialize(RadioSetGuiImpl.java:35)
        at com.goldencode.p2j.ui.client.WidgetRegistry.reconstructWidget(WidgetRegistry.java:129)
        at com.goldencode.p2j.ui.client.WidgetRegistry.pushDefinition(WidgetRegistry.java:386)
        at com.goldencode.p2j.ui.chui.ThinClient$19.run(ThinClient.java:7331)
        at com.goldencode.p2j.ui.chui.ThinClient.eventBracket(ThinClient.java:13668)
        at com.goldencode.p2j.ui.chui.ThinClient.eventDrawingBracket(ThinClient.java:13609)
        at com.goldencode.p2j.ui.chui.ThinClient.pushOneDef(ThinClient.java:7319)
        at com.goldencode.p2j.ui.chui.ThinClient.pushScreenDefinition(ThinClient.java:7267)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:497)
        at com.goldencode.p2j.util.MethodInvoker.invoke(MethodInvoker.java:76)
        at com.goldencode.p2j.net.Dispatcher.processInbound(Dispatcher.java:705)
        at com.goldencode.p2j.net.Conversation.block(Conversation.java:319)
        at com.goldencode.p2j.net.Conversation.waitMessage(Conversation.java:257)
        at com.goldencode.p2j.net.Queue.transactImpl(Queue.java:1128)
        at com.goldencode.p2j.net.Queue.transact(Queue.java:585)
        at com.goldencode.p2j.net.BaseSession.transact(BaseSession.java:223)
        at com.goldencode.p2j.net.HighLevelObject.transact(HighLevelObject.java:163)
        at com.goldencode.p2j.net.RemoteObject$RemoteAccess.invokeCore(RemoteObject.java:1425)
        at com.goldencode.p2j.net.InvocationStub.invoke(InvocationStub.java:97)
        at com.sun.proxy.$Proxy4.standardEntry(Unknown Source)
        at com.goldencode.p2j.main.ClientCore.start(ClientCore.java:277)
        at com.goldencode.p2j.main.ClientCore.start(ClientCore.java:100)
        at com.goldencode.p2j.main.ClientDriver.start(ClientDriver.java:201)
        at com.goldencode.p2j.main.CommonDriver.process(CommonDriver.java:398)
        at com.goldencode.p2j.main.ClientDriver.process(ClientDriver.java:95)
        at com.goldencode.p2j.main.ClientDriver.main(ClientDriver.java:267)
...

#1387 Updated by Constantin Asofiei over 8 years ago

Eugenie Lyzenko wrote:

Some recent 1811r changes cause the GUI regression in horizontal radio-set. NPE in AbstractGuiDriver, line 2525:
[...]
The problem is ews == null This is called from AbstractGuiDriver.getTextWidth(). The issue log:
[...]

There is a change in how text metrics are retrieved. Never call gd.getTextWidth/Height directly. Instead, use the new AbstractWidget.getText[Width/Height] and AbstractWidget.getText[Width/Height]Native. These ensure the window is selected before calling gd.getTextWidth/Height and released after works is done. The difference between these two APIs is this: the native part will retrieve the metrics as reported by the driver, and the non-native version will first interrogate the legacy metrics in the text-metrics.xml. So, use the native version if you were calling GuiDriver APIs directly (originally) and the non-native version if you were relying on FontManager APIs to retrieve the text metrics (note that the FontManager first checks the legacy metrics and if not found, it calls the driver versions).

#1388 Updated by Eugenie Lyzenko over 8 years ago

Constantin Asofiei wrote:

There is a change in how text metrics are retrieved. Never call gd.getTextWidth/Height directly. Instead, use the new AbstractWidget.getText[Width/Height] and AbstractWidget.getText[Width/Height]Native. These ensure the window is selected before calling gd.getTextWidth/Height and released after works is done. The difference between these two APIs is this: the native part will retrieve the metrics as reported by the driver, and the non-native version will first interrogate the legacy metrics in the text-metrics.xml. So, use the native version if you were calling GuiDriver APIs directly (originally) and the non-native version if you were relying on FontManager APIs to retrieve the text metrics (note that the FontManager first checks the legacy metrics and if not found, it calls the driver versions).

OK. I've made the changes in radio-set GUI code and it works.

Greg,

Can I commit this change to 1811r? Only GUI code involved, no testing required.

#1389 Updated by Hynek Cihlar over 8 years ago

Constantin Asofiei wrote:

Hynek Cihlar wrote:

Constantin Asofiei wrote:

Please go ahead, I'm not working on it.

Constantin, I have compared 1811r revision 10950 and 10949 and didn't find any functional difference of V/F. I am probably missing something. Also I don't have an up to date converted Majic source so I connected to the server running under your account.

This is a real regression, as is not present in trunk. To duplicate, use frame fo activated via v in this test:
[...]
I'll pick it up tomorrow morning.

Sorry, I must have been comparing wrong versions when testing this. Indeed this was a regression. A fix is checked in to 1811r, revision 10970.

#1390 Updated by Eugenie Lyzenko over 8 years ago

Task branch 1811r for review updated to revision 10971.

Fix for window title bar issues. Had to remake svg images to adjust new title size.

#1391 Updated by Constantin Asofiei over 8 years ago

Hynek Cihlar wrote:

Constantin Asofiei wrote:

Hynek Cihlar wrote:

Constantin Asofiei wrote:

Please go ahead, I'm not working on it.

Constantin, I have compared 1811r revision 10950 and 10949 and didn't find any functional difference of V/F. I am probably missing something. Also I don't have an up to date converted Majic source so I connected to the server running under your account.

This is a real regression, as is not present in trunk. To duplicate, use frame fo activated via v in this test:
[...]
I'll pick it up tomorrow morning.

Sorry, I must have been comparing wrong versions when testing this. Indeed this was a regression. A fix is checked in to 1811r, revision 10970.

Does the test in note 1328 work? Because I still see a problem in MAJIC, ( F/M from main-menu) and use the radio-set.

#1392 Updated by Constantin Asofiei over 8 years ago

Constantin Asofiei wrote:

Does the test in note 1328 work? Because I still see a problem in MAJIC, ( F/M from main-menu) and use the radio-set.

Hynek, 10970 breaks this... please take a look.

#1393 Updated by Hynek Cihlar over 8 years ago

Constantin Asofiei wrote:

Constantin Asofiei wrote:

Does the test in note 1328 work? Because I still see a problem in MAJIC, ( F/M from main-menu) and use the radio-set.

Hynek, 10970 breaks this... please take a look.

Constantin, I have been working on this. I have a fix ready, just a few more tests and will check in.

#1394 Updated by Hynek Cihlar over 8 years ago

Hynek Cihlar wrote:

Constantin Asofiei wrote:

Constantin Asofiei wrote:

Does the test in note 1328 work? Because I still see a problem in MAJIC, ( F/M from main-menu) and use the radio-set.

Hynek, 10970 breaks this... please take a look.

Constantin, I have been working on this. I have a fix ready, just a few more tests and will check in.

A fix checked in to 1811r revision 10972.

#1395 Updated by Greg Shah over 8 years ago

Can I commit this change to 1811r? Only GUI code involved, no testing required.

Yes. Unless the branch is locked, you may check in any "safe" changes. This means the build doesn't break and you have done enough testing to be reasonably confident you aren't causing major regressions.

#1396 Updated by Greg Shah over 8 years ago

Only GUI code involved, no testing required.

It is OK to make non-GUI or GUI changes to a task branch without running regression testing. I just want you to do enough manual testing to know the change is safe enough. Otherwise it might cause others working on that branch to have a broken build.

#1397 Updated by Eugenie Lyzenko over 8 years ago

Task branch 1811r for review updated to revision 10973.

Adding fix for NPE regression in radio-set. Tested with current radio-set testcases and it works fine.

#1398 Updated by Eugenie Lyzenko over 8 years ago

Task branch 1811r for review updated to revision 10974.

The logo icon size/location fix.

#1399 Updated by Eugenie Lyzenko over 8 years ago

Task branch 1811r for review updated to revision 10975.

The inactive frame title color fix.

#1400 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811r Revisions 10945-10974

I've made some formatting/coding standards changes and cleaned up all of the history entries. Those changes are in revision 10975. Otherwise I don't see anything of concern.

Consider task branch 1811r to be LOCKED for testing at this time. When it passes testing, we will merge to trunk and then create task branch 1811s. You may make a temporary branch off of 1811r for development that is done between now and when we open 1811s. Plan to you will NOT check in those changes to 1811r but rather that you will do a bzr merge --uncommitted of the pending work into the new 1811s.

#1401 Updated by Sergey Ivanovskiy over 8 years ago

Greg, found that for each of drawing circles, for example, invoking on a key pressed, we redraw images, because an image hash code is changed for new created image. Please, look at GuiWebEmulatedWindow

   private final Object[] encodeImage(int x, int y, ImageWrapper wrappedImage)
   {
      VirtualScreen virtualScreen = webdriver.getVirtualScreen();
      int imageHash = wrappedImage.getImage().hashCode();
      ImageEncoding encoding;
      byte[] encodedImage;
      if (webdriver.addImageUsage(imageHash, windowId))
      {
         encoding = ImageEncoding.RAW;
         virtualScreen.drawImage(wrappedImage, x, y);
         encodedImage = new RawEncoder().packToBinaries(virtualScreen, x, y,
                  wrappedImage.getWidth(), wrappedImage.getHeight());
      }
      else
      {
         encoding = ImageEncoding.HASH;
         encodedImage = null;
      }

      return new Object[] {imageHash, encoding, encodedImage};
   }

Does we need to build images each time? At the beginning of this task I used MD5 hash function to calculate the image hash but then you advice to change it to the java object hash code. VisualVM sampler proves that is very expensive.

#1402 Updated by Greg Shah over 8 years ago

Does we need to build images each time?

No, the whole point of the caching was to only ever build it once and send it once. From then on, we only want to tell the client to render the image by ID.

At the beginning of this task I used MD5 hash function to calculate the image hash but then you advice to change it to the java object hash code.

I may have forgotten, but I don't remember suggesting the Java Object hash code as an ID. I was just suggesting a unique ID. If I said to use the Java Object hash code I apologize because it is a bad idea. The Java Object hash code is definitely not unique. Why not just register each new image with a sequential, unique int that only goes with that image?

#1403 Updated by Greg Shah over 8 years ago

From note 805:

1. Is there a need to send a hash of the image? Why not a simple 32-bit integer as an ID? The images are not being edited once they are loaded. So after the first time it is pushed down to the client, all other times drawing needs to happen the ID can be sent instead.

By "simple 32-bit integer as an ID", I meant the creation of your own counter/sequence. I was not intending to suggest use of the object hash code.

#1404 Updated by Sergey Ivanovskiy over 8 years ago

Okay, I am planning to fix it, but at this place we don't know its unique id, we have only the BufferedImage and it is supposed the image is unchanged.

#1405 Updated by Sergey Ivanovskiy over 8 years ago

Ah, it is due to function drawHelper.processImage(ps); changes some images (creates new ones). Thus it is truly my bug. It needs to cache an image with some other parameters from the ps structure.

#1406 Updated by Sergey Ivanovskiy over 8 years ago

Greg, please review the diff, the caching is based on the java hash code due to the draw image operation could provide new image parameters and it could transform the image.

   public void drawImage(ImageWrapper img,
                         int          x,
                         int          y,
                         int          w,
                         int          h,
                         int          xoff,
                         int          yoff,
                         boolean      transparent,
                         boolean      stretch,
                         boolean      retain,
                         boolean      convert3D)

This diff if applied to 1811r improves the web client performance. If you are planning the presentation it is necessary to add.

#1407 Updated by Greg Shah over 8 years ago

Eugenie reported the following regarding the 1811r branch runtime regression testing (conversion testing was not needed):

The testing completed. No regressions have found. The results - 1811r_10976_06069cc_20151010_evl.zip. Had to start 3-way CTRL-C test in separate session.

Task branch 1811r has been merged to trunk (as revision 10945) and then was archived.

Created task branch 1811s from P2J trunk revision 10945.

#1408 Updated by Greg Shah over 8 years ago

Code Review caching_images_20151010.txt

1. The PaintStructure is meant to be a class that is a simple container for public data. Adding an inner class and methods is not compatible with that idea. Please move the EmbeddedImageStructure to a separate class. You can get the similar results by just providing a EmbeddedImageStructure constructor that takes a PaintStructure as a parameter. PaintStructure should not contain EmbeddedImageStructure or even references to it.

2. I still don't understand why we need to use object hash codes as the ID. Can't we just add private static int counter = 0; and private final int uniqueId = counter++; to ImageWrapper? Then this uniqueId always matches that image. Use uniqueId as your map keys. If there is a reason my approach doesn't make sense, please help me understand the reasons.

3. Why does DRAW_IMAGE pass transformImage == true? Is there a reason to always transform the image with every drawing? My original idea was that only the first time DRAW_IMAGE is executed, would we create (and cache) the image. Then subsequent DRAW_IMAGE invocations would just reuse the cached image.

This diff if applied to 1811r improves the web client performance. If you are planning the presentation it is necessary to add.

Don't worry. I intend to get 1811s into the trunk before the demo.

Please apply your changes to 1811s.

#1409 Updated by Sergey Ivanovskiy over 8 years ago

Code Review caching_images_20151010.txt

2. I still don't understand why we need to use object hash codes as the ID. Can't we just add private static int counter = 0; and private final int uniqueId = counter++; to ImageWrapper? Then this uniqueId always matches that image. Use uniqueId as your map keys. If there is a reason my approach doesn't make sense, please help me understand the reasons.

Your approach can be used if there is an agreement that there don't exist two drawImage/setIcon invocations with the same image and with the different drawing parameters.
The problem is if we get an image as a parameter of the drawImage, then this image must be transformed if the provided image parameters are new, otherwise if the original image with these parameters has been used before by drawImage invocations, then the transformed image must be taken from the cache and must be sent to the client with its width and its height. I am trying to avoid the image width and height caching on the client side.

3. Why does DRAW_IMAGE pass transformImage == true? Is there a reason to always transform the image with every drawing? My original idea was that only the first time DRAW_IMAGE is executed, would we create (and cache) the image. Then subsequent DRAW_IMAGE invocations would just reuse the cached image.

The original image is unchanged, but the image parameters can vary from the wide set. The image parameters change the bounds of the transformed embedded image and this bound should be send to the client.

We can use your approach if the client will cache the image bounds with the image itself and there don't exist two drawImage/setIcon invocation with the same image but with the different drawing parameters. The decision to avoid images bounds caching is due to I think it is possible to use one image with the different drawing parameters.

#1410 Updated by Greg Shah over 8 years ago

The problem is if we get an image as a parameter of the drawImage, then this image must be transformed if the provided image parameters are new, otherwise if the original image with these parameters has been used before by drawImage invocations, then the transformed image must be taken from the cache and must be sent to the client with its width and its height.

But it seems that your changes always call getSubimage() if transformImage == true. That means that DRAW_IMAGE is always working with a new BufferedImage instance.

If the width/height/xoffset/yoffset is the same as a cached version, then shouldn't we be re-using the same exact image from the cache?

Make each ImageWrapper have their own image ID. Then use the image ID, width, height, xoffset and yoffset to detect if something is already in the cache.

Make sure that DRAW_IMAGE usages that match all of these values will reuse the same image.

The original image is unchanged, but the image parameters can vary from the wide set. The image parameters change the bounds of the transformed embedded image and this bound should be send to the client.

This processing can still be done on the server. We just need to create a unique key that matches the 5 values (image ID, width, height, xoffset and yoffset). I don't think a hash code is the right approach, since it is intended for Object hashcode to have collisions. We want a key that doesn't collide with others.

#1411 Updated by Sergey Ivanovskiy over 8 years ago

Greg, please review the committed revision 10946. The EmbeddedImageStructure is not required, we can move List<Integer> getObjectSeal() to PaintStructure or we can change ClientImageDrawHelper to use this class in order to be consistent and complete.

#1412 Updated by Sergey Ivanovskiy over 8 years ago

Greg, please clarify the clipboard task, does it need to develop the client side clipboard like it is implemented by web chui client or the server side? Does it include the conversion task to support CLIPBOARD:VALUE? Now, this testcase testcases/uast/clipboard/clipboard_basic.p is failed during conversion

Exception: Unsupported method or attribute KW_VALUE. [COLON id <17179869481> 32:13]
     [java]     at com.goldencode.p2j.pattern.CommonAstSupport$Library.throwException(CommonAstSupport.java:2346)
     [java]     at com.goldencode.p2j.pattern.CommonAstSupport$Library.throwException(CommonAstSupport.java:2331)
     [java]     at com.goldencode.expr.CE9456.execute(Unknown Source)
     [java]     at com.goldencode.expr.Expression.execute(Expression.java:341)
     [java]     ... 30 more

I think that we should register listener for focus and selection events on the server side and notify clients with prepare clipboard message, thus the message MSG_CLIPBOARD_PREPARE should be from the server not from the client. Then the clipboard logic will be consistent. The web client initiates selection by mouse and key messages to server, the server changes its state (focus and selection) and sends its back to the client. And for drawing GUI element on the canvas the logic is exactly this one.

#1413 Updated by Sergey Ivanovskiy over 8 years ago

Checking the web client keys SHIFT + INSERT (1022) , SHIFT + DELETE (639), CTRL+INSERT (2558) gives SHIFT + DELETE (127) due to it has no label. Fixed committed revision 10947. There is the same issue for Swing GUI client. Fixed in committed revision 10948. Now combinations SHIFT + INSERT (1022) , SHIFT + DELETE (639), CTRL+INSERT (2558) and their functions work properly for Swing GUI.

#1414 Updated by Greg Shah over 8 years ago

Greg, please clarify the clipboard task, does it need to develop the client side clipboard like it is implemented by web chui client or the server side?

Current clipboard implementation is detailed in the following:

ChUI imple: 605, 609-647, 752, 766-769, 775-778, 826-827, 847
GUI imple: 722-723

In 752 I describe the difference in approaches between ChUI clipboard and GUI. The key point is that the ChUI implementation is NOT designed to implement any kind of 4GL support. The approach is to replace the selection/copy/paste behavior in normal terminal emulator software. It is provided as a convenience to the user, but it is not essential. For select/copy we are just selecting/copying the screen characters themselves. For paste, we are just sending the clipboard contents to the "keyboard buffer" (on the Java side). There is no widget-level implementation for ChUI and there is no integration with the CLIPBOARD system handle.

In GUI, the copy/cut/paste functionality is tightly coupled to the 4GL widget behavior and is also integrated with the CLIPBOARD system handle.

I "roughed" out the implementation already as is described in notes 722-723. However, there are TODOs and open issues listed there that must be fixed. There will be changes need on both the JS and Java sides.

Does it include the conversion task to support CLIPBOARD:VALUE? Now, this testcase testcases/uast/clipboard/clipboard_basic.p is failed during conversion

The CLIPBOARD system handle is task #1807. I prefer not to work it right now because the app code we are converting has had this usage removed.

For now, I prefer you to just fix clipboard support as can be used from widgets like FILL-IN and EDITOR.

I think that we should register listener for focus and selection events on the server side and notify clients with prepare clipboard message, thus the message MSG_CLIPBOARD_PREPARE should be from the server not from the client.

Please don't use the term "server" to refer to the Java client. It is confusing because when we talk about server we think "P2J application server". The Java client is an embedded web server, but we try not to refer to it that way since "server" is usually thought of differently.

Yes, I agree that we may have to send the current selection from the Java client down to the JS side whenever it changes. That way the JS code has direct access to the current selection from within the CTRL-V or CTRL-X clipboard event handler. The key is to avoid the up-call to the Java client, since we can't block to wait for the response and we MUST return the text from within that clipboard event handler otherwise the clipboard cannot be written to.

#1415 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811s Revision 10946

This is significantly better. However there are some small issues to resolve.

1. I don't want to add getEmbeddedImageStructure() to PaintStructure. Please just create a static factory method EmbeddedImageStructure.create(PaintStructure<F, I> ps).

2. Is BufferImageDrawHelper.calculateImageBounds() safe if ps.stretchToFit true and ps.retainShape true and kHor == kVer? It will execute the same code as the kHor > kVer.

3. ImageWrapper.getImageUniqueId() should be placed at the end of the file.

4. The ImageWrapper import should use wildcards.

5. Please add history entries for all files you have edited. This includes the revisions 10947 and 10948. As you edit files in the future, please check if there is already a new history entry added for this branch. If so, add your text to that. If not, add a history entry. Otherwise someone (me) has to do this for everyone as part of a final review. It takes me longer to reconstruct the date of your changes and the intention, that if you just do this as you go.

#1416 Updated by Eugenie Lyzenko over 8 years ago

Task branch 1811s for review updated to revision 10949.

The scrollbar drawing issues fixes.

#1417 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811s Revision 10949

Well done! I fixed a merge problem with a history entry (task branch 1811s revision 10950), but otherwise it looks very good.

I guess there was no size issue? Perhaps it was just an artifact of some font sizing differences exposed in the EDITOR widget of the testcase.

#1418 Updated by Eugenie Lyzenko over 8 years ago

Greg Shah wrote:

Code Review Task Branch 1811s Revision 10949

I guess there was no size issue? Perhaps it was just an artifact of some font sizing differences exposed in the EDITOR widget of the testcase.

There was 1 pixel sizing issue. The reason is rough intermediate calculation from double to int. I've changed some local vars to double to make good precision.

#1419 Updated by Sergey Ivanovskiy over 8 years ago

Committed revision 10951, 10952 is according to the code review Task Branch 1811s Revision 10946.

#1420 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811s Revision 10952

1. In BufferedImageDrawHelper.processImage() and BufferedImageDrawHelper.calculateImageBounds(), please name the parameter struct instead of embeddedImageStructure. The use of embeddedImageStructure is so verbose that it makes the code hard to read. Using very long names is usually not a good thing as it makes the reader have to work harder to read the same code. This is especially true when the variable is used everywhere in a method such that the entire method has almost twice as much text.

2. The private EmbeddedImageStructure.hasConversion() should be at the end of the file.

3. The static EmbeddedImageStructure.create() should be just after the constructor.

#1421 Updated by Sergey Ivanovskiy over 8 years ago

Code Review Task Branch 1811s Revision 10952

Fixed in 10953. How to summarize these code rules? For me they look like these private methods must be at the end of the class module, constructors and factory methods are at the beginning after fields declarations.

#1422 Updated by Greg Shah over 8 years ago

Fixed in 10953. How to summarize these code rules? For me they look like these private methods must be at the end of the class module, constructors and factory methods are at the beginning after fields declarations.

See "Structuring Java Class Files" in handbook/coding_stds/approaches.html.

#1423 Updated by Sergey Ivanovskiy over 8 years ago

See "Structuring Java Class Files" in handbook/coding_stds/approaches.html.

Ok. See it is really important because it helps to organize the code that is sometimes produced by copy&past or by aesthetic feeling that I think is opposite to the technical design.

#1424 Updated by Eugenie Lyzenko over 8 years ago

Task branch 1811s for review updated to revision 10954.

The tooltip issues fix. For painting coordinates and disappearing issues. Also ThinClient was changed to avoid tooltip setup for frame, alert-box, dialog-box and window widgets. All of them does not have the tooltip.

#1425 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811s Revision 10954

The changes look reasonable, I don't have a problem with them. My only concern is that the use of tooltips will be especially expensive in the web GUI since now the entire window redraws whenever a tooltip is hidden.

To get things to perform well, we may have to do this in a smarter way (e.g. detect what exactly needs to redraw) OR possibly simply use a separate top-level window for drawing the tooltip, such that hiding that special window will not require any redrawing of the owner's window (the native implementation of windows in both Swing and the web already handle the redrawing of the already rendered content when overlapping content is removed.

For now, don't do anything more on this. My thoughts above are just being written down for future reference.

#1426 Updated by Eugenie Lyzenko over 8 years ago

Greg Shah wrote:

Code Review Task Branch 1811s Revision 10954

The changes look reasonable, I don't have a problem with them. My only concern is that the use of tooltips will be especially expensive in the web GUI since now the entire window redraws whenever a tooltip is hidden.

To get things to perform well, we may have to do this in a smarter way (e.g. detect what exactly needs to redraw) OR possibly simply use a separate top-level window for drawing the tooltip, such that hiding that special window will not require any redrawing of the owner's window (the native implementation of windows in both Swing and the web already handle the redrawing of the already rendered content when overlapping content is removed.

For now, don't do anything more on this. My thoughts above are just being written down for future reference.

OK. Agree, this is not the best aprooach. I thought about something like:
1. Save area behind tooltip before drawing it as bitmap image.
2. Draw saved image back when we need to hide the tooltip.

Or implement something like "invalid region" and repaint only widgets intersecting this region.

#1427 Updated by Eugenie Lyzenko over 8 years ago

Task branch 1811s for review updated to revision 10955.

Fix for abend in focusing frame with no focusable widget.

#1428 Updated by Sergey Ivanovskiy over 8 years ago

Good news is the Firefox (Chrome, Explorer and Opera) supports synthetic "copy&cut" commands, but "paste" doesn't. Working on this scenario for web GUI: 1) The JS client permits these keys combinations CTRL+C / CTRL+X (copy / paste) to reach the web client and prevents the default action for these keys. 2) The web client fills its clipboard and sends the content to the JS client 3) The JS client performs synthetic "copy&cut" commands 4) CTRL+V reaches the web client after "clipboard paste" event has occurred and after the paste text has been send to the web client to fill its clipboard.

#1429 Updated by Constantin Asofiei over 8 years ago

1811s rev 10956 contains first pass at window resize in web. What is left is enlarging the window via the north and west borders - I think is some JS peculiarity related to how events are raised.

#1430 Updated by Eugenie Lyzenko over 8 years ago

Task branch 1811s for review updated to revision 10957.

Fix GUI issues in message area.

#1431 Updated by Sergey Ivanovskiy over 8 years ago

Good news is the Firefox (Chrome, Explorer and Opera) supports synthetic "copy&cut" commands, but "paste" doesn't. Working on this scenario for web GUI: 1) The JS client permits these keys combinations CTRL+C / CTRL+X (copy / paste) to reach the web client and prevents the default action for these keys. 2) The web client fills its clipboard and sends the content to the JS client 3) The JS client performs synthetic "copy&cut" commands 4) CTRL+V reaches the web client after "clipboard paste" event has occurred and after the paste text has been send to the web client to fill its clipboard.

Greg, I performed some tests and it follows that synthetic "copy&cut" commands work only if they are invoked by user

syntheticCopySucceeded = document.execCommand("copy", false, "");

For example, if the button document.getElementById("copyButton") with onclick handler generates synthetic "copy&cut" commands, it works only if it is clicked manually, document.getElementById("copyButton").click(); doesn't initiate real copy action. It is due to sucuirity restriction in the browser. As alternative, we can add Copy/Cut button on the Toolbar panel if we want to copy the application selection to OS clipboard, from OS clipboard to application it works on Ctrl+V. From application editor selection it is possible to copy/cut to application clipboard and then to paste to another application editor. Another method is CTRL+INSERT/SHIFT+DELETE are used for application and CTRL+C/CTRL+V for OS. The problem is only to copy from application to OS clipboard. What do you think? Are there another solutions?

#1432 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811s Revision 10955

I'm OK with the changes.

#1433 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811s Revision 10957

I'm good with the changes.

#1434 Updated by Greg Shah over 8 years ago

Greg, I performed some tests and it follows that synthetic "copy&cut" commands work only if they are invoked by user

This is consistent with how it is documented.

For example, if the button document.getElementById("copyButton") with onclick handler generates synthetic "copy&cut" commands, it works only if it is clicked manually, document.getElementById("copyButton").click(); doesn't initiate real copy action. It is due to sucuirity restriction in the browser.

I understand.

As alternative, we can add Copy/Cut button on the Toolbar panel if we want to copy the application selection to OS clipboard

I don't understand. Your idea about always sending the current selection from the Java client to the JS side is a good one. If the current selected text is already on the JS side, then why can't we just read that from the p2j.clipboard_helpers.js getSelectedText() function? We would not have to send a MSG_CLIPBOARD_PREPARE and everything would be synchronous with the real CTRL-C or CTRL-X clipboard events.

from OS clipboard to application it works on Ctrl+V

This is what I expected. You are saying that PASTE already works for GUI?

Another method is CTRL+INSERT/SHIFT+DELETE are used for application and CTRL+C/CTRL+V for OS.

Both CTRL+INSERT/SHIFT+DELETE and CTRL-C/CTRL-X must work the same way. The keys MUST provide proper clipboard integration directly. But as far as I understand it, the browsers DO generate proper clipboard events for all of these cases, right?

So it is just a matter of having the selection.

In the CUT case, we must also notify the Java client to clear the selection, but that can be done as a notification.

#1435 Updated by Sergey Ivanovskiy over 8 years ago

As alternative, we can add Copy/Cut button on the Toolbar panel if we want to copy the application selection to OS clipboard

I don't understand. Your idea about always sending the current selection from the Java client to the JS side is a good one. If the current selected text is already on the JS side, then why can't we just read that from the p2j.clipboard_helpers.js getSelectedText() function? We would not have to send a MSG_CLIPBOARD_PREPARE and everything would be synchronous with the real CTRL-C or CTRL-X clipboard events.

At first I tried to implement logic with synthetic copy/cut. This diff uses the current editors selection from EditorGuiImpl and FillInGuiImpl to send to the JS client, if there are no current selection and we press CTRL + C/CTRL + Insert it invokes the unimplemented method sendClipboardContents from p2j.clipboard.js. But it seems to work if there is the current selection or pressed CTRL + V (paste from OS clipboard). It needs to test because it occasionally fails for some keys sequences.

#1436 Updated by Constantin Asofiei over 8 years ago

1811s rev 10958 finishes window resize in Web.

Sergey, I don't recall exactly, but I think keeping a cursor arrow pressed (i.e. right-arrow in a editor) used to work and advance the caret. Now you need to press and release the arrow, otherwise the caret does not move. Can you take a look?

#1437 Updated by Sergey Ivanovskiy over 8 years ago

Sergey, I don't recall exactly, but I think keeping a cursor arrow pressed (i.e. right-arrow in a editor) used to work and advance the caret. Now you need to press and release the arrow, otherwise the caret does not move. Can you take a look?

Ok, planning to fix it.

#1438 Updated by Greg Shah over 8 years ago

if there are no current selection and we press CTRL + C/CTRL + Insert it invokes the unimplemented method sendClipboardContents from p2j.clipboard.js

Can you confirm that the clipboard events are being generated properly by the browser?

I don't understand why sendClipboardContents() would get invoked. It backs the MSG_READ_CLIPBOARD which only gets invoked by clipboardContents() which is only called during a KA_PASTE event in FILL-IN or EDITOR.

#1439 Updated by Sergey Ivanovskiy over 8 years ago

if there are no current selection and we press CTRL + C/CTRL + Insert it invokes the unimplemented method sendClipboardContents from p2j.clipboard.js

Can you confirm that the clipboard events are being generated properly by the browser?

I don't understand why sendClipboardContents() would get invoked. It backs the MSG_READ_CLIPBOARD which only gets invoked by clipboardContents() which is only called during a KA_PASTE event in FILL-IN or EDITOR.

I have checked that the current version 10958 generates copy/cut/paste events correctly. It can be checked by test.html from testcases/uast/keyboards/web if follows readme.txt

#1440 Updated by Greg Shah over 8 years ago

I have checked that the current version 10958 generates copy/cut/paste events correctly.

This is true even when there is no selection?

If so, then we are either sending the wrong events to the Java side OR the Java side is somehow interpreting the events incorrectly.

#1441 Updated by Greg Shah over 8 years ago

Sergey: If your code is safe to check in, perhaps you should commit it so that we can both be looking at the same sources.

#1442 Updated by Sergey Ivanovskiy over 8 years ago

Sergey: If your code is safe to check in, perhaps you should commit it so that we can both be looking at the same sources.

Ok, planning to commit it within a hour. But first I will fix the auto-repeat issue.

#1443 Updated by Sergey Ivanovskiy over 8 years ago

Sergey, I don't recall exactly, but I think keeping a cursor arrow pressed (i.e. right-arrow in a editor) used to work and advance the caret. Now you need to press and release the arrow, otherwise the caret does not move. Can you take a look?

It is auto-repeat issue. Committed revision 10959 should fix it for the Swing client.

#1444 Updated by Sergey Ivanovskiy over 8 years ago

Committed revision 10960 should fix auto-repeats for the web client and "code cleanup" is here.

#1445 Updated by Sergey Ivanovskiy over 8 years ago

Sergey: If your code is safe to check in, perhaps you should commit it so that we can both be looking at the same sources.

Committed revision 10961 should support clipboard. Greg, what do you think if we add new event MSG_CURRENT_SELECTION to WebClientMessageTypes instead of using MSG_WRITE_CLIPBOARD?

#1446 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811s Revision 10961

I made some minor cleanups, checked in as 10962.

1. p2j.clipboard.js needs a history entry.

2. In regard to your question:

what do you think if we add new event MSG_CURRENT_SELECTION to WebClientMessageTypes instead of using MSG_WRITE_CLIPBOARD?

MSG_WRITE_CLIPBOARD will be needed for CLIPBOARD:VALUE assignment support. Let's split off the setting of the current selection, from the MSG_WRITE_CLIPBOARD. That way, we won't have to add back the functionality when we find a way to implement MSG_WRITE_CLIPBOARD.

Approach:

  • Add MSG_CURRENT_SELECTION to WebClientMessageTypes, leaving MSG_WRITE_CLIPBOARD in place.
  • In p2j.clipboard.js, the function writeClipboard() should revert back to its old implementation (the one from 10960).
  • Add GuiWebSocket.setSelection() and send a MSG_CURRENT_SELECTION message.
  • In p2j.socket.js, add processing for MSG_CURRENT_SELECTION and call a new function in p2j.clipboard.js called setSelection(). That code should have the current writeClipboard() implementation (the one from 10961).

#1447 Updated by Eugenie Lyzenko over 8 years ago

Task branch 1811s for review updated to revision 10963.

Fix for message area scrollbar buttons. Had to modify approach for very small buttons.

#1448 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811s Revision 10963

It looks good.

#1449 Updated by Sergey Ivanovskiy over 8 years ago

Committed revision 10964 is prepared for review.

#1450 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811s Revision 10964

It looks good.

Is the "no selection" case fixed at this point or is there still a problem?

#1451 Updated by Constantin Asofiei over 8 years ago

Hynek, there are unexpected scollbars in ask-gui.p, with 1811s.

#1452 Updated by Greg Shah over 8 years ago

there are unexpected scollbars in ask-gui.p, with 1811s.

Are you referring to the same problem as noted in #2752 ?

#1453 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

there are unexpected scollbars in ask-gui.p, with 1811s.

Are you referring to the same problem as noted in #2752 ?

Yes

#1454 Updated by Sergey Ivanovskiy over 8 years ago

Is the "no selection" case fixed at this point or is there still a problem?

It isn't reproduced now, the problem is only if there is no selection and we press the copy keys and then press the paste keys. The saved before selection is inserted. But checked that the gedit conducts the same manner and decided to leave it.
There is a general problem if we press keys rapidly, the browser ui thread can come in to the stale state or frozen and then it wakes up occasionally. I think the last bug is known and reproducible. The swing gui client has the same issue that the key input becomes frozen. testcases/uast/demo/demo_widgets.p was used for this test.

#1455 Updated by Greg Shah over 8 years ago

There is a general problem if we press keys rapidly, the browser ui thread can come in to the stale state or frozen and then it wakes up occasionally. I think the last bug is known and reproducible. The swing gui client has the same issue that the key input becomes frozen.

Please create a new task with parent task #2677. Add myself as a watcher. Make sure to document a recreate.

I'm going to close out the clipboard item. The CLIPBOARD:VALUE will be worked outside of 1811.

I assume you will work on the key performance issue next?

#1456 Updated by Constantin Asofiei over 8 years ago

1811s rev 10956 10965 contains a change in custom font management: if the font is BOLD and there is no custom definition for BOLD font (but there is a font definition for its non-BOLD flavour), then use the non-bold definition and let the style use bold. This is needed because some fonts (like Microsoft Sans Serif) do not have a bold definition true-type file.

#1457 Updated by Sergey Ivanovskiy over 8 years ago

There is a general problem if we press keys rapidly, the browser ui thread can come in to the stale state or frozen and then it wakes up occasionally. I think the last bug is known and reproducible. The swing gui client has the same issue that the key input becomes frozen.

Please create a new task with parent task #2677. Add myself as a watcher. Make sure to document a recreate.

I assume you will work on the key performance issue next?

Created this difficult #2759 task and planning to use visualvm tool to investigate the issue this evening.

#1458 Updated by Greg Shah over 8 years ago

I assume you will work on the key performance issue next?

Created this difficult #2759 task and planning to use visualvm tool to investigate the issue this evening.

I was referring to "performance lag between pressing keys and seeing results drawn in web client". Can #2759 wait until later or is that the same thing?

#1459 Updated by Sergey Ivanovskiy over 8 years ago

May be it is the same thing. Ok, I will focus on performance lag.

#1460 Updated by Eugenie Lyzenko over 8 years ago

Task branch 1811s for review updated to revision 10966.

The fix for SELECTION-LIST sizing issues. The important logic in selection-list restored. Also fixed small issue in scrollbar bottom-right intersection area location(when both scrollbars are visible).

#1461 Updated by Greg Shah over 8 years ago

Rebased task branch 1811s from P2J trunk revision 10946. The current revision in 1811s is now 10967.

#1462 Updated by Sergey Ivanovskiy over 8 years ago

The standard demo_widgets.p at login produces the attached web screen without the combo box label. If we open its items list, then its label appears/is drawn on the screen.

#1463 Updated by Sergey Ivanovskiy over 8 years ago

It is interesting to check how the web drawing performance is changed if CanvasRenderer.prototype.drawPixel = function(ctx, x, y, color) is replaced on something like drawImage but with saved ctx.getImageData(this.origin.x + x, this.origin.y + y, width, height) data. Also planning to check how the performance lag between keys and seeing results is changed if we wrap some drawing primitives with setTimeout(primitive,2).

#1464 Updated by Eugenie Lyzenko over 8 years ago

Constantin,

I'm not sure this question for you or not, just saw you works on font implementation. Today after rebase may be the title font in P2J was changed to something that is difficult to read. See the pictures attached.

The question: is it possible to easily change the font back in config files? Or at least to something that will be good to read.

#1465 Updated by Constantin Asofiei over 8 years ago

Eugenie Lyzenko wrote:

Constantin,

I'm not sure this question for you or not, just saw you works on font implementation. Today after rebase may be the title font in P2J was changed to something that is difficult to read. See the pictures attached.

The question: is it possible to easily change the font back in config files? Or at least to something that will be good to read.

Please update your directory.xml and simple/server/fonts from the testcases project. If you don't want to replace the directory.xml, update your custom-fonts with the one from here: #2701-4

This should fix the drawing issue.

#1466 Updated by Constantin Asofiei over 8 years ago

Sergey Ivanovskiy wrote:

The standard demo_widgets.p at login produces the attached web screen without the combo box label. If we open its items list, then its label appears/is drawn on the screen.

The label is drawn once at the correct y, but after that is drawn way lower. I'm looking into it.

#1467 Updated by Constantin Asofiei over 8 years ago

Sergey Ivanovskiy wrote:

The standard demo_widgets.p at login produces the attached web screen without the combo box label. If we open its items list, then its label appears/is drawn on the screen.

There was a bug in computing the intersected rectangle while clipping. Fixed in 1811s rev 10968

#1468 Updated by Sergey Ivanovskiy over 8 years ago

Sergey Ivanovskiy wrote:

It is interesting to check how the web drawing performance is changed if CanvasRenderer.prototype.drawPixel = function(ctx, x, y, color) is replaced on something like drawImage but with saved ctx.getImageData(this.origin.x + x, this.origin.y + y, width, height) data. Also planning to check how the performance lag between keys and seeing results is changed if we wrap some drawing primitives with setTimeout(primitive,2).

Substituting a line of pixels with its image isn't succeeded. It is difficult to synchronize ctx.putImageData(Pixel, x, y) with
ctx.putImageData(Screen, 0, 0, x, y, width, height). The idea was to do one invocation of putImageData instead of multiple ones to draw horizontal and vertical lines

function fillRectIn(screenMemory, scanline, x, y, x1, y1, color)
{
   var w = x1 - x;
   var h = y1 - y;
   console.log("x=" + x + " y=" + y +  " w=" + w + " h=" + h);
   w = Math.max(w, 1);
   h = Math.max(h, 1);
   var path = [];
   var jump = scanline - w;
   var p = (y - 0.5) * scanline + (x - 0.5); //unclear how to position inside screen data

   for (var y0 = 0; y0 < h; y0++)
   {
      for (var x0 = 0; x0 < w; x0++)
      {
         screenMemory[4 * p] = color[0];
         screenMemory[4 * p + 1] = color[1];
         screenMemory[4 * p + 2] = color[2];
         screenMemory[4 * p + 3] = 255;
         p++;
         path.push({x: x0 + x, y: y0 + y});
      }
      p += jump;
   }
   return path;
}

, where screen = ctx.getImageData(0, 0, this.canvas.width, this.canvas.height); and screenMemory = screen.data;
Decided to stop developing this idea.

#1469 Updated by Greg Shah over 8 years ago

Decided to stop developing this idea.

It was a good idea to try.

Is the key processing lag caused by drawing performance issues?

#1470 Updated by Sergey Ivanovskiy over 8 years ago

Is the key processing lag caused by drawing performance issues?

If we press rapidly keys the browser gui thread becomes frozen, but after several seconds of inactivity all pressed keys appear. I haven't determine yet what is the method that makes gui thread busy. It can be drawing messages. The collected data by the Firefox gives
putImageData as invocation of drawPixel as invocation of drawLineSegment takes 43.92% percent of CPU (16465 invocations),
Gecko takes 19.3% CPU (is it websocket?), Graphics takes 18.3% CPU (includes setFont, processWindowFocus, drawText, MouseHandler, processMouseMove), drawImage takes 5.36%, WidenPathRenderer takes 4.78%.

Is it necessarily to take the read key lock if we send data to the JS client?

=== modified file 'src/com/goldencode/p2j/ui/client/driver/web/WebClientProtocol.java'
--- src/com/goldencode/p2j/ui/client/driver/web/WebClientProtocol.java    2015-09-23 22:44:57 +0000
+++ src/com/goldencode/p2j/ui/client/driver/web/WebClientProtocol.java    2015-10-15 17:14:06 +0000
@@ -393,12 +393,9 @@
     */   
    public void sendBinaryMessage(byte[] message) 
    {
-      synchronized (lock)
+      if (pushWorker != null)
       {
-         if (pushWorker != null)
-         {
-            pushWorker.pushMessage(ByteBuffer.wrap(message));
-         }
+         pushWorker.pushMessage(ByteBuffer.wrap(message));
       }
    }


Two threads, the "main" UI thread from the java web and the browser UI thread are connected via bi-directional websocket channel.
May be the synchronous wait for response from one of the part can lead to UI unresponsiveness. It needs to check that it helps if synchronous requests will be handled by the separate non UI thread.

#1471 Updated by Constantin Asofiei over 8 years ago

1811s rev 10969 fixes the frame scroll issue and fill-in size in #2752 and #2704.

#1472 Updated by Greg Shah over 8 years ago

If we press rapidly keys the browser gui thread becomes frozen, but after several seconds of inactivity all pressed keys appear.

I think it is important to try to measure the time between sending a given key to the Java side and when the drawing for that key is sent down to the JS side. That will tell us how much of the delay is on the Java side (or in the up/down network transfers).

#1473 Updated by Sergey Ivanovskiy over 8 years ago

I think it is important to try to measure the time between sending a given key to the Java side and when the drawing for that key is sent down to the JS side. That will tell us how much of the delay is on the Java side (or in the up/down network transfers).

Ok, planning to use gd.setCurrentSelection(text) to send new coming keys back and check the time.

#1474 Updated by Sergey Ivanovskiy over 8 years ago

I think it is important to try to measure the time between sending a given key to the Java side and when the drawing for that key is sent down to the JS side. That will tell us how much of the delay is on the Java side (or in the up/down network transfers).

Ok, planning to use gd.setCurrentSelection(text) to send new coming keys back and check the time.

Greg, please look at the results, it measures the time and the sequence of events comings between pressing key 'a' on the editor and response from editor on key event with 'a' using gd.setCurrentSelection(String.valueof(ke.charCode()));
17:44:25.823 [2] a takes 8 p2j.clipboard.js:258:1
17:44:26.935 [3] a takes 6 p2j.clipboard.js:258:1
The results shows that the key events delays are increased if we press 'a' rapidly and the order of keys is not preserved in logs.

#1475 Updated by Sergey Ivanovskiy over 8 years ago

Added keys measure tests diff. Planning to measure the time between WebGuiDriver readKey and setCurrentSelection invocation.

#1476 Updated by Greg Shah over 8 years ago

Based on your results, how much of the delays are due to the Java round trip?

#1477 Updated by Sergey Ivanovskiy over 8 years ago

Greg Shah wrote:

Based on your results, how much of the delays are due to the Java round trip?

The java side delays are also increased but the differences are not calculated by this test due to unstructured simple messages like 'a'.
I don't understand why the order of keys is changed on JS client.

#1478 Updated by Sergey Ivanovskiy over 8 years ago

Added tests diff.

#1479 Updated by Sergey Ivanovskiy over 8 years ago

Sergey Ivanovskiy wrote:

Greg Shah wrote:

Based on your results, how much of the delays are due to the Java round trip?

The java side delays are also increased but the differences are not calculated by this test due to unstructured simple messages like 'a'.
I don't understand why the order of keys is changed on JS client.

The problem in my test code it needs to take the object from the arrays head var keyObj = p2j.keys_queue.shift();.
Thus the result is the web java takes [48] 97 takes 11284 ms but the JS client takes [48] a takes 24214 ms. The half of time is used by web java side.

#1480 Updated by Greg Shah over 8 years ago

OK, we are going to postpone further work on this until later. The client is working well enough for now. We have time set aside later to work on performance.

Please shift to the implementation of "XOR compositing and DOTTED_LARGE line stroke".

#1482 Updated by Greg Shah over 8 years ago

If I recall correctly, direct drawing of pixels using putImageData() does not honor compositing. It just overwrites whatever pixels are there.

Since we do our own line stroking, I think we need to implement the XOR ourselves.

#1484 Updated by Constantin Asofiei over 8 years ago

regression in note 1483 is from 1811s rev 10969 - I'm looking into it.

#1485 Updated by Eugenie Lyzenko over 8 years ago

Constantin Asofiei wrote:

1811s rev 10969 fixes the frame scroll issue and fill-in size in #2752 and #2704.

Constantin, I have the similar issue with recent changes in 1811s with standalone app.

Please test demo/demo_widgets.p with this version. I have the picture attached and this is certainly a kind of regression. If you have another picture - please post it here.

#1486 Updated by Constantin Asofiei over 8 years ago

Regressions in notes 1483 and 1485 are fixed in 1811s rev 10971 - I've checked both ask-gui.p and demo_widgets.p.

#1487 Updated by Greg Shah over 8 years ago

Revision 10972 implements moveToTop()/moveToBottom() at the driver level. A useful testcase is frame-z-order/zw1.p.

The web client works exactly as in Progress.

The Swing client still as a bug: any window brought to the top obtains the focus (it should not to match Progress).

Constantin: would you please do a code review of the changes? I'm especially worried about the SwingEmulatorWindow changes pertaining to the focus listeners and focus processing. You will see that I attempt to manually restore the focus to the window that lost it (but it doesn't work).

#1488 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

Constantin: would you please do a code review of the changes? I'm especially worried about the SwingEmulatorWindow changes pertaining to the focus listeners and focus processing. You will see that I attempt to manually restore the focus to the window that lost it (but it doesn't work).

A possible issue I'm seeing is why SwingGuiDriver.moveToBottom doesn't disable focus monitoring, as moveToTop does? Otherwise, I'll take a deeper look tomorrow morning my time.

#1489 Updated by Eugenie Lyzenko over 8 years ago

Task branch 1811s for review updated to revision 10973.

This is the small fix for combo-box drop-down button when the BGCOLOR attribute is changed for combo-box.

#1490 Updated by Constantin Asofiei over 8 years ago

Constantin Asofiei wrote:

Greg Shah wrote:

Constantin: would you please do a code review of the changes? I'm especially worried about the SwingEmulatorWindow changes pertaining to the focus listeners and focus processing. You will see that I attempt to manually restore the focus to the window that lost it (but it doesn't work).

A possible issue I'm seeing is why SwingGuiDriver.moveToBottom doesn't disable focus monitoring, as moveToTop does? Otherwise, I'll take a deeper look tomorrow morning my time.

Greg, beside this, rev 10972 looks OK.

Also, I don't think there is a way to alter the window z-order at the OS level in Swing: using toFront activates the window, too, and I can't find a way to change this. The only way to control the window z-order in Swing is if we parent the windows to a JDesktopPane.

#1491 Updated by Sergey Ivanovskiy over 8 years ago

Constantin, Greg, I am planning to fix inconsistency in the JS clipping region processing. Please, look at usages of drawPixel, the particular draw line/rectangle function returns the path of pixels that is used by the stroke manager if it needs to extend the path. But clipping regions if they are checked inside putImageData are not taken into account since the path is calculated on the upper level. I need to fix it because of new XOR implementation. Planning to delegate all drawing by LineStrokes to CanvasRenderer

CanvasRenderer.prototype.drawPixel = function(ctx, x, y, color)
{
   this.pixData[0] = color[0];
   this.pixData[1] = color[1];
   this.pixData[2] = color[2];

   this.putImageData(this.ctx, this.pixel, x, y);
};

CanvasRenderer.prototype.putImageData = function(ctx, pixel, x, y)
{
   var lastClip = this.lastClippedRegion(true);

   if (lastClip != undefined)
   {
      var top    = lastClip.y;
      var bottom = lastClip.y + lastClip.height - 1;
      var left   = lastClip.x;
      var right  = lastClip.x + lastClip.width - 1;

      if (y < top || y > bottom || x < left || x > right)
      {
         return;
      }
   }

   ctx.putImageData(pixel, x, y);
};

#1492 Updated by Sergey Ivanovskiy over 8 years ago

Constantin, what do you think it is correct to check clipping regions at the upper level to place it inside each of the drawing functions? Or to place it inside p2j.screen.js at the screen level.

#1493 Updated by Sergey Ivanovskiy over 8 years ago

To eliminate developing deadlock planning to implement cutLine and cutRectangle function to intersect with clipping region and then will notify if it is suitable and the pixels layout is ok.

#1494 Updated by Constantin Asofiei over 8 years ago

Sergey Ivanovskiy wrote:

To eliminate developing deadlock planning to implement cutLine and cutRectangle function to intersect with clipping region and then will notify if it is suitable and the pixels layout is ok.

This looks OK, as we need to intersect the line/rectangle with the current clipping region. The code in putImageData works for some cases (for example, this fixes problems in drawing the combo's drop-down), but for some others it does not.

Is there another way to set pixels on the canvas, except ctx.putImageData?

#1495 Updated by Constantin Asofiei over 8 years ago

Sergey, I've just noticed something: ctx.putImageData is called from p2j.strokes.js:
  1. DotsPathRenderer.applyStroke
  2. WidenPathRenderer.applyStroke

What about this: place the same check as in CanvasRenderer.putImageData in these two locations:

var lastClip = <canvas-renderer>.lastClippedRegion(true);

   if (lastClip != undefined)
   {
      var top    = lastClip.y;
      var bottom = lastClip.y + lastClip.height - 1;
      var left   = lastClip.x;
      var right  = lastClip.x + lastClip.width - 1;

      if (y < top || y > bottom || x < left || x > right)
      {
         return;
      }
   }

This should solve the problems...

#1496 Updated by Sergey Ivanovskiy over 8 years ago

Sergey, I've just noticed something: ctx.putImageData is called from p2j.strokes.js:
  1. DotsPathRenderer.applyStroke
  2. WidenPathRenderer.applyStroke

What about this: place the same check as in CanvasRenderer.putImageData in these two locations:
[...]

This should solve the problems...

Yes, agree with you, planning to delegate the put pixels to CanvasRenderer.

#1497 Updated by Sergey Ivanovskiy over 8 years ago

Constantin Asofiei wrote:

Sergey Ivanovskiy wrote:

To eliminate developing deadlock planning to implement cutLine and cutRectangle function to intersect with clipping region and then will notify if it is suitable and the pixels layout is ok.

This looks OK, as we need to intersect the line/rectangle with the current clipping region. The code in putImageData works for some cases (for example, this fixes problems in drawing the combo's drop-down), but for some others it does not.

Is there another way to set pixels on the canvas, except ctx.putImageData?

I think that except of p2j.strokes.js there is only one based function that works with pixels it is

CanvasRenderer.prototype.drawLineSegment = function(ctx, x1, y1, x2, y2, color)

Thus if we apply clipping region here it would be the same as to apply it by
CanvasRenderer.prototype.putImageData = function(ctx, pixel, x, y)

What do you think it is correct?

#1498 Updated by Sergey Ivanovskiy over 8 years ago

Committed revision 10974 prepared for review, added the dot large stroke style and refactored CanvasRenderer and LineStrokes classes to reduce the number of invocations of intersecting with the last clipping region.

#1499 Updated by Sergey Ivanovskiy over 8 years ago

Before the rev 10974 this redraw bug is present. 1) Run demo_widgets.p 2) Select item from the fist combobox 3) The result is white thin shade line on the background screen is leaved, but if do the same actions for the second combobox, the window or the frame that owns combobox is redrawn.

#1500 Updated by Greg Shah over 8 years ago

Have you noticed that the currently selected canvas now has a selection highlight around it? I think this started recently. I wonder if it was the recent change to make some event handlers register on document instead of window:

http://stackoverflow.com/questions/9815518/wp7-html5-how-to-prevent-a-canvas-from-being-selected-highlighted/9838199#9838199

Sergey: What was the reason for the change?

#1501 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

Have you noticed that the currently selected canvas now has a selection highlight around it? I think this started recently. I wonder if it was the recent change to make some event handlers register on document instead of window:

http://stackoverflow.com/questions/9815518/wp7-html5-how-to-prevent-a-canvas-from-being-selected-highlighted/9838199#9838199

Sergey: What was the reason for the change?

The document event handlers were added to allow window resize: the canvas is not notified of mouse move if the mouse moves outside of it... so dragging is not possible with event handlers added to it.

#1502 Updated by Sergey Ivanovskiy over 8 years ago

Greg Shah wrote:

Have you noticed that the currently selected canvas now has a selection highlight around it? I think this started recently. I wonder if it was the recent change to make some event handlers register on document instead of window:

http://stackoverflow.com/questions/9815518/wp7-html5-how-to-prevent-a-canvas-from-being-selected-highlighted/9838199#9838199

Sergey: What was the reason for the change?

Recently I have changed p2j.keyboards.js

   /**
    * Initialize module.
    * 
    * @param    {Object} cfg
    *           The client configuration
    */
   me.init = function(cfg)
   {      
      if (keyboardReader)
      {
         document.removeEventListener('keydown', keyboardReader.onkeydown, true);
         document.removeEventListener('keypress', keyboardReader.onkeypress, true);
         document.removeEventListener('keyup', keyboardReader.onkeyup, true);
      }

I can roll back these changes.

#1503 Updated by Greg Shah over 8 years ago

I don't want anything rolled back just yet.

The document event handlers were added to allow window resize: the canvas is not notified of mouse move if the mouse moves outside of it... so dragging is not possible with event handlers added to it.

Couldn't the same event processing occur with the handlers registered on window instead of document?

#1504 Updated by Sergey Ivanovskiy over 8 years ago

I don't want anything rolled back just yet.

The document event handlers were added to allow window resize: the canvas is not notified of mouse move if the mouse moves outside of it... so dragging is not possible with event handlers added to it.

Couldn't the same event processing occur with the handlers registered on window instead of document?

Have checked this diff and haven't found any Look&Feel changes, and finally don't understand this problem. There is the small dotted border around the window indicating that it can be focused by the keyboard and this condition is given by p2j.screen.js Window constructor

      // to make a window can be focused
      options.tabIndex = options.tabIndex || 1;

=== modified file 'src/com/goldencode/p2j/ui/client/gui/driver/web/res/p2j.mouse.js'
--- src/com/goldencode/p2j/ui/client/gui/driver/web/res/p2j.mouse.js    2015-10-14 11:10:46 +0000
+++ src/com/goldencode/p2j/ui/client/gui/driver/web/res/p2j.mouse.js    2015-10-16 13:29:45 +0000
@@ -284,16 +284,16 @@
    {
       // window border
       var mouseOp = rops[idx];
-      document.addEventListener(mouseOp, processWindowBorder(mouseOp));
+      window.addEventListener(mouseOp, processWindowBorder(mouseOp));
    }

    // dragging operations
-   document.addEventListener("click",       processMouseDragStop ("click"));
-   document.addEventListener("contextmenu", processMouseDragStop ("contextmenu"));
-   document.addEventListener("dblclick",    processMouseDragStop ("dblclick"));
-   document.addEventListener("mousedown",   processMouseDragStart("mousedown"));
-   document.addEventListener("mouseup",     processMouseDragStop ("mouseup"));
-   document.addEventListener("mousemove",   processMouseDrag     ("mousemove"));
+   window.addEventListener("click",       processMouseDragStop ("click"));
+   window.addEventListener("contextmenu", processMouseDragStop ("contextmenu"));
+   window.addEventListener("dblclick",    processMouseDragStop ("dblclick"));
+   window.addEventListener("mousedown",   processMouseDragStart("mousedown"));
+   window.addEventListener("mouseup",     processMouseDragStop ("mouseup"));
+   window.addEventListener("mousemove",   processMouseDrag     ("mousemove"));

    // leave/out listeners are not required for dragging purposes.

#1505 Updated by Sergey Ivanovskiy over 8 years ago

Greg, please could you provide me with the 4GL program where the XOR composite is used to test the XOR code? It is used by BrowserGuiImpl.

#1506 Updated by Stanislav Lomany over 8 years ago

The testcase for XOR is uast/browse/gui/browse-gui-stat1.p. If you need designed fonts too, there is fonts-table.txt.

#1507 Updated by Greg Shah over 8 years ago

Nice catch Sergey! Revision 10977 removes the setting of tabIndex. I don't see any other bad consequences of this, so I think the selection highlight issue is cleared.

There is no needed to worry about the document vs window issue. It is unrelated. Thanks again.

#1508 Updated by Eugenie Lyzenko over 8 years ago

Task branch 1811s for review updated to revision 10978.

This is the fix for demo/calc.p. Now it works.

#1509 Updated by Eugenie Lyzenko over 8 years ago

Warning! The 1811s revision 10977 has a regression in FillInGuimpl with foreground text color.

The screen is attached.

#1510 Updated by Constantin Asofiei over 8 years ago

Eugenie Lyzenko wrote:

Warning! The 1811s revision 10977 has a regression in FillInGuimpl with foreground text color.

The screen is attached.

Fixed in 1811s rev 10979.

#1511 Updated by Hynek Cihlar over 8 years ago

Eugenie Lyzenko wrote:

Task branch 1811s for review updated to revision 10978.

This is the fix for demo/calc.p. Now it works.

Frame.frameScroll should inherit the size from its parent - the frame. This happens only when frameScroll isn't explicitly set the size (see AbstractContainer.width()/height()). You shouldn't set the size of frameScroll as it should always be the size of its parent (and thus follow the same sizing logic) but instead make sure FrameConfig.widthChars and FrameConfig.heightChars values are propagated to Frame.size.

#1512 Updated by Constantin Asofiei over 8 years ago

Review for 1811s rev 10977: although this fixes calc.p, I'm still concerned that we have different logic executed when the FRAME's size attributes are changed via handle (when FrameWidget is used) or via FRAME fname:size-attribute (when GenericFrame is used).

Hynek: do you think is OK for GenericFrame to just delegate the work to FrameWidget (with some checks if we are inside setup or not)?

#1513 Updated by Hynek Cihlar over 8 years ago

Constantin Asofiei wrote:

Review for 1811s rev 10977: although this fixes calc.p, I'm still concerned that we have different logic executed when the FRAME's size attributes are changed via handle (when FrameWidget is used) or via FRAME fname:size-attribute (when GenericFrame is used).

Hynek: do you think is OK for GenericFrame to just delegate the work to FrameWidget (with some checks if we are inside setup or not)?

Maybe, but this would for sure require some extensive testing. The frame position/sizing logic is currently a bit of its own world in respect to the rest of the server (and client) side widgets. This area would certainly need more think time.

#1514 Updated by Eugenie Lyzenko over 8 years ago

Hynek,

You shouldn't set the size of frameScroll as it should always be the size of its parent (and thus follow the same sizing logic) but instead make sure FrameConfig.widthChars and FrameConfig.heightChars values are propagated to Frame.size.

This does not work. The Frame.afterConfigUpdate() refreshes frame internal parts every time the config is changing. Except frameScroll, which keep the old values and this was one part of the issue(in addition to width/height change refusing).

The frame position/sizing logic is currently a bit of its own world in respect to the rest of the server (and client) side widgets.

Please clarify what do you mean. The frame is a kind of widget with exact size/position(even if both are unknown or 0). Every time for both client and server side we have to have the current size/position that can be obtained from widget and are reflected to the actual size/position we see on the screen. Right?

#1515 Updated by Sergey Ivanovskiy over 8 years ago

The testcase for XOR is uast/browse/gui/browse-gui-stat1.p. If you need designed fonts too, there is fonts-table.txt.

Stanislav, could you help? Does it need a special setup? I have tested this program and for swing gui it is more stable but for web it appears for several seconds and then fails due to this server log (clients log has only disconnect errors)

[10/16/2015 20:57:32 MSK] (SecurityManager:FINER) {00000000:0000000B:standard} Sending LOCALUSERID: bogus
[10/16/2015 20:57:33 MSK] (com.goldencode.p2j.ui.FontTable:SEVERE) Could not load the legacy font metrics from file font-metrics-ext.xml
[10/16/2015 20:57:33 MSK] (com.goldencode.p2j.ui.FontTable:SEVERE) Could not load the legacy text metrics from file text-metrics.xml
[10/16/2015 20:57:33 MSK] (com.goldencode.p2j.ui.FontTable:SEVERE) Could not load the custom font [Segue UI] from file fonts/segoeuibd.ttf
[10/16/2015 20:57:35 MSK] (LogicalTerminal:WARNING) No client parameters are set: calling the client-side to determine the driver type is expensive!
[10/16/2015 20:57:35 MSK] (LogicalTerminal:WARNING) No client parameters are set: calling the client-side to determine the driver type is expensive!
[10/16/2015 20:57:36 MSK] (com.goldencode.p2j.ui.FontTable:SEVERE) Text [0] has no legacy metrics with font [ms sans serif,8,false,false,false].
[10/16/2015 20:57:36 MSK] (com.goldencode.p2j.ui.FontTable:SEVERE) Text [,] has no legacy metrics with font [ms sans serif,8,false,false,false].
[10/16/2015 20:57:36 MSK] (com.goldencode.p2j.ui.FontTable:SEVERE) Text [f1] has no legacy metrics with font [ms sans serif,8,false,false,false].
[10/16/2015 20:57:36 MSK] (com.goldencode.p2j.ui.FontTable:SEVERE) Text [f2] has no legacy metrics with font [ms sans serif,8,false,false,false].
[10/16/2015 20:57:36 MSK] (com.goldencode.p2j.ui.FontTable:SEVERE) Text [f3] has no legacy metrics with font [ms sans serif,8,false,false,false].
[10/16/2015 20:57:37 MSK] (com.goldencode.p2j.ui.FontTable:SEVERE) Text [f1] has no legacy metrics with font [ms sans serif,8,true,false,false].
[10/16/2015 20:57:38 MSK] (com.goldencode.p2j.ui.FontTable:SEVERE) Text [Static Browse] has no legacy metrics with font [tahoma,8,true,false,false].
[10/16/2015 20:57:38 MSK] (com.goldencode.p2j.ui.FontTable:SEVERE) Text [1] has no legacy metrics with font [ms sans serif,8,true,false,false].
[10/16/2015 20:57:38 MSK] (com.goldencode.p2j.ui.FontTable:SEVERE) Text [2] has no legacy metrics with font [ms sans serif,8,true,false,false].
[10/16/2015 20:57:38 MSK] (com.goldencode.p2j.ui.FontTable:SEVERE) Text [3] has no legacy metrics with font [ms sans serif,8,true,false,false].
[10/16/2015 20:57:38 MSK] (com.goldencode.p2j.ui.FontTable:SEVERE) Text [4] has no legacy metrics with font [ms sans serif,8,true,false,false].
[10/16/2015 20:57:38 MSK] (com.goldencode.p2j.ui.FontTable:SEVERE) Text [5] has no legacy metrics with font [ms sans serif,8,true,false,false].
[10/16/2015 20:57:38 MSK] (com.goldencode.p2j.ui.FontTable:SEVERE) Text [6] has no legacy metrics with font [ms sans serif,8,true,false,false].
[10/16/2015 20:57:38 MSK] (com.goldencode.p2j.ui.FontTable:SEVERE) Text [7] has no legacy metrics with font [ms sans serif,8,true,false,false].
[10/16/2015 20:57:38 MSK] (com.goldencode.p2j.ui.FontTable:SEVERE) Text [8] has no legacy metrics with font [ms sans serif,8,true,false,false].
[10/16/2015 20:57:38 MSK] (com.goldencode.p2j.ui.FontTable:SEVERE) Text [9] has no legacy metrics with font [ms sans serif,8,true,false,false].
[10/16/2015 20:57:38 MSK] (com.goldencode.p2j.ui.FontTable:SEVERE) Text [10] has no legacy metrics with font [ms sans serif,8,true,false,false].
[10/16/2015 20:57:39 MSK] (StandardServer.invoke:SEVERE) {00000002:0000000D:bogus} Abnormal end!

#1516 Updated by Sergey Ivanovskiy over 8 years ago

At first I have lost this exception in the client log

END DRAWING CYCLE FOR WINDOW 1
Oct 16, 2015 8:57:39 PM Dispatcher.processInbound() 
SEVERE: {main} Unexpected throwable.
java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at com.goldencode.p2j.util.MethodInvoker.invoke(MethodInvoker.java:76)
    at com.goldencode.p2j.net.Dispatcher.processInbound(Dispatcher.java:705)
    at com.goldencode.p2j.net.Conversation.block(Conversation.java:319)
    at com.goldencode.p2j.net.Conversation.waitMessage(Conversation.java:257)
    at com.goldencode.p2j.net.Queue.transactImpl(Queue.java:1128)
    at com.goldencode.p2j.net.Queue.transact(Queue.java:585)
    at com.goldencode.p2j.net.BaseSession.transact(BaseSession.java:223)
    at com.goldencode.p2j.net.HighLevelObject.transact(HighLevelObject.java:163)
    at com.goldencode.p2j.net.RemoteObject$RemoteAccess.invokeCore(RemoteObject.java:1425)
    at com.goldencode.p2j.net.InvocationStub.invoke(InvocationStub.java:97)
    at com.sun.proxy.$Proxy11.standardEntry(Unknown Source)
    at com.goldencode.p2j.main.ClientCore.start(ClientCore.java:277)
    at com.goldencode.p2j.main.ClientCore.start(ClientCore.java:100)
    at com.goldencode.p2j.main.ClientDriver.start(ClientDriver.java:201)
    at com.goldencode.p2j.main.CommonDriver.process(CommonDriver.java:398)
    at com.goldencode.p2j.main.ClientDriver.process(ClientDriver.java:95)
    at com.goldencode.p2j.main.ClientDriver.main(ClientDriver.java:267)
Caused by: java.lang.NullPointerException
    at com.goldencode.p2j.ui.client.gui.driver.web.GuiWebSocket.processMouseWidgets(GuiWebSocket.java:1845)
    at com.goldencode.p2j.ui.client.gui.driver.web.GuiWebDriver.registerMouseWidgets(GuiWebDriver.java:996)
    at com.goldencode.p2j.ui.client.TypeAhead.getKeystroke(TypeAhead.java:324)
    at com.goldencode.p2j.ui.chui.ThinClient.waitForEvent(ThinClient.java:12667)
    at com.goldencode.p2j.ui.chui.ThinClient.waitForWorker(ThinClient.java:10686)
    at com.goldencode.p2j.ui.chui.ThinClient.waitFor(ThinClient.java:10269)
    at com.goldencode.p2j.ui.chui.ThinClient.waitFor(ThinClient.java:10223)
    ... 21 more

#1517 Updated by Constantin Asofiei over 8 years ago

Sergey Ivanovskiy wrote:

At first I have lost this exception in the client log
[...]

This one is mine, for some reason a widget is null. I'm looking into it.

#1518 Updated by Hynek Cihlar over 8 years ago

Eugenie Lyzenko wrote:

Hynek,

You shouldn't set the size of frameScroll as it should always be the size of its parent (and thus follow the same sizing logic) but instead make sure FrameConfig.widthChars and FrameConfig.heightChars values are propagated to Frame.size.

This does not work. The Frame.afterConfigUpdate() refreshes frame internal parts every time the config is changing. Except frameScroll, which keep the old values and this was one part of the issue(in addition to width/height change refusing).

frameScroll.size should be equal to BaseConfig.INV_COORD to "inherit" its effective size value from its parent, that is from frame.size. If frameScroll.height() or frameScroll.width() don't give the expected values then the reason is that frame.size doesn't hold the expected value either. If frame.size doesn't hold the expected value it's most likely because it is not in sync with FrameConfig.heightChars and FrameConfig.widthChars.

The frame position/sizing logic is currently a bit of its own world in respect to the rest of the server (and client) side widgets.

Please clarify what do you mean. The frame is a kind of widget with exact size/position(even if both are unknown or 0). Every time for both client and server side we have to have the current size/position that can be obtained from widget and are reflected to the actual size/position we see on the screen. Right?

I meant that the frame sizing/positioning logic is implemented differently than the rest of the widgets - window, fill-in, browse, etc.

#1519 Updated by Sergey Ivanovskiy over 8 years ago

Committed revision 10980 should support new composite drawing ops for web.

#1520 Updated by Greg Shah over 8 years ago

Hynek Cihlar wrote:

Constantin Asofiei wrote:

Review for 1811s rev 10977: although this fixes calc.p, I'm still concerned that we have different logic executed when the FRAME's size attributes are changed via handle (when FrameWidget is used) or via FRAME fname:size-attribute (when GenericFrame is used).

Hynek: do you think is OK for GenericFrame to just delegate the work to FrameWidget (with some checks if we are inside setup or not)?

Maybe, but this would for sure require some extensive testing. The frame position/sizing logic is currently a bit of its own world in respect to the rest of the server (and client) side widgets. This area would certainly need more think time.

Eugenie: for now please put TODOs into both the FrameWidget and GenericFrame size attribute methods to explain that we want to unify the logic into a common method instead of having 2 places with slightly different logic. Also please state that until that unified version exists, one may need to make changes in both places.

#1521 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811s Revision 10980

It is good. The only concern I have is whether the getImageData() should honor the current transform for X and Y (this.origin.x + x, this.origin.y + y)?

#1522 Updated by Sergey Ivanovskiy over 8 years ago

It is good. The only concern I have is whether the getImageData() should honor the current transform for X and Y (this.origin.x + x, this.origin.y + y)?

Yes, since CanvasRenderer.putImageData is used with prepared actual point (x, y).

#1523 Updated by Greg Shah over 8 years ago

since CanvasRenderer.putImageData is used with prepared actual point (x, y)

Are you saying that it is safe because any usage of CanvasRenderer.putImageData() must pass X and Y values that are already transformed in regard to the origin OR they are intended to operate on an untransformed basis (e.g. resize)?

If so, then please modify the javadoc to explain this and put a comment in the code to note that no transform is needed.

#1524 Updated by Sergey Ivanovskiy over 8 years ago

Committed revision 10982 added this comment that putImageData uses (x,y) canvas coordinates.

#1525 Updated by Sergey Ivanovskiy over 8 years ago

Constantin, Greg, I found a regression if the window is opened we can't drag the task bar. It is due to function processMouseDragStart(mouseOp) (p2j.mouse.js) prevents the drag mouse events propagation. But if the window is minimized. This event comes to the task bar panel

         // determine the top-most visible window
         var win = p2j.screen.topVisibleWindow()
         if (win == undefined)
         {
            // no window is shown
            return; // returned here if the window is minimized
         }

What do you think it needs to fix?

#1526 Updated by Eugenie Lyzenko over 8 years ago

Task branch 1811s for review updated to revision 10984.

Adding TODO comments in both GenericFrame(setters for frame widget) and BaseEntity(setters for other widgets).

#1527 Updated by Sergey Ivanovskiy over 8 years ago

Constantin, there is the bug it is easy to decrease the window size but it is difficult to increase it again. It is due to the calculated resize differences are based on the cursor style which becomes the default during the drag operation. I can fix it tomorrow if you agree.

#1528 Updated by Constantin Asofiei over 8 years ago

Sergey Ivanovskiy wrote:

Constantin, there is the bug it is easy to decrease the window size but it is difficult to increase it again. It is due to the calculated resize differences are based on the cursor style which becomes the default during the drag operation. I can fix it tomorrow if you agree.

Yes, please do.

#1529 Updated by Greg Shah over 8 years ago

Sergey Ivanovskiy wrote:

Constantin, Greg, I found a regression if the window is opened we can't drag the task bar. It is due to function processMouseDragStart(mouseOp) (p2j.mouse.js) prevents the drag mouse events propagation. But if the window is minimized. This event comes to the task bar panel
[...]
What do you think it needs to fix?

Yes, I guess so. But if it is going to take a while, then we may defer this. Tooltip support for the taskbar is an optional feature and we have other work to get done.

#1530 Updated by Sergey Ivanovskiy over 8 years ago

Constantin, there is the bug it is easy to decrease the window size but it is difficult to increase it again. It is due to the calculated resize differences are based on the cursor style which becomes the default during the drag operation. I can fix it tomorrow if you agree.

Yes, please do.

Constantin, please look at the mouse resize changes by committed revision 10989, added the peer canvas to represent the resize of the original window. It gives possibility to smooth the resize of the original window.

#1531 Updated by Sergey Ivanovskiy over 8 years ago

Greg Shah wrote:

Constantin, Greg, I found a regression if the window is opened we can't drag the task bar. It is due to function processMouseDragStart(mouseOp) (p2j.mouse.js) prevents the drag mouse events propagation. But if the window is minimized. This event comes to the task bar panel
[...]
What do you think it needs to fix?

Yes, I guess so. But if it is going to take a while, then we may defer this. Tooltip support for the taskbar is an optional feature and we have other work to get done.

Committed revision 10989 should fix resize and the task panel drag&drop.

#1532 Updated by Sergey Ivanovskiy over 8 years ago

Fixed misspelled names committed revision 10990.

#1533 Updated by Greg Shah over 8 years ago

Sergey: for 1811s, it is time to re-test the GUI testscases that we are using for our manual GUI regression testing. I will retest these in Swing and I would like you to retest in the web GUI client. I am including the latest list below, but there are some items that we are excluding because they are already known to be broken. If you find a problem, let me know via email and I will confirm if this is a known issue or not. You will want to compare the behavior with the trunk (one exception: the toggle box case which will work better in the trunk than in 1811s since I have been holding off on the rebase, in the hope that 2560a and 2564c will be merged to trunk soon.

Make sure your font environment is correct. Capture and apply the text metrics if necessary.

./ask-gui.p
./hello.p
./demo/calc.p
./demo/calc-static.p
./demo/calc-static-chars.p
./demo/calc-static-pixels.p
./toggle_box/gui/tbx_present.p
./combo_box/combo_box9_1.p
./rectangle/rect_test2.p
./rectangle/rect_test6.p
./rectangle/rect_test7_1.p
./image/image0.p
./image/image10_1.p
./button/gui_btn_test3.p
./button/gui_btn_test4.p
./button/gui_btn_test5.p
./demo/movie-ratings-dynamic.p
./demo/movie-ratings-static.p
./demo/simple_windows.p
./demo/demo_widgets.p
./window_sizing/create_empty_window.p
./window_sizing/default_empty_window.p
./window_sizing/test_runner.p
./simple_alert_box.p (don't test, this is known to be broken)
./simpler_alert_box.p (don't test, this is known to be broken)
./message-update6.p
./minimal_view_dynamic_widget.p (don't test, this is known to be broken)
./menu/simple_sm.p
./menu/popup_ext.p
./window_parenting/waitfor_2wnd.p
./frame-z-order/zw1.p
./browse/gui/browse-gui-stat1.p

#1534 Updated by Constantin Asofiei over 8 years ago

Sergey Ivanovskiy wrote:

Fixed misspelled names committed revision 10990.

I'm OK with the changes, good work.

#1535 Updated by Sergey Ivanovskiy over 8 years ago

Make sure your font environment is correct. Capture and apply the text metrics if necessary.

Greg, please help how to check the font environment? It seems that I have incorrect fonts.

#1536 Updated by Greg Shah over 8 years ago

Please review #2701-4

#1537 Updated by Greg Shah over 8 years ago

Rebased task branch 1811s from P2J trunk revision 10948. The current revision in 1811s is now 10994.

DO NOT pull down these changes yet.

Hynek: I had the following questions when rebasing.

  • Is the addition of FrameGuiImpl.defaultFocus() still correct considering the changes in 2560a?
  • Is the realized processing in FrameGuiImpl.setVisible() correctly merged or should it move considering the changes in 2560a?
  • The new WindowResized and WindowMoved event support that was in WindowGuiImpl was moved to TopLevelWindow. It doesn't seem right, but the code just doesn't fit into the 2560a version of WindowGuiImpl.processEvent(). Is that correct?
  • There is a conflicting change in delimiter processing for ZeroColumnLayout.processStacked(), not sure if the rebased version is correct.
  • I had to change usage of Window.resolveWindow() in both MessageAreaGuiImpl.doLayout() and ToolTip.hide(). I'm not sure if the change is correct.

Each of these cases is "marked" by a pair of comments starting REBASE-TODO-START and ending in REBASE-TODO-END. Please review each of these cases and make changes as necessary. Once you are comfortable that the changes make sense, please remove the markers and notify everyone here that it is safe to pull down the changes.

Before you start, I recommend doing this review in a new branch so that the current 1811s can still be used as a reference.

#1538 Updated by Hynek Cihlar over 8 years ago

Greg Shah wrote:

Rebased task branch 1811s from P2J trunk revision 10948. The current revision in 1811s is now 10994.

DO NOT pull down these changes yet.

Hynek: I had the following questions when rebasing.

  • Is the addition of FrameGuiImpl.defaultFocus() still correct considering the changes in 2560a?

I think FrameGuiImpl.defaultFocus() is ok how and where it is. But the related Window.defaultFocus() should be moved to TopLevelWindow.

  • Is the realized processing in FrameGuiImpl.setVisible() correctly merged or should it move considering the changes in 2560a?

This seems to be ok.

  • The new WindowResized and WindowMoved event support that was in WindowGuiImpl was moved to TopLevelWindow. It doesn't seem right, but the code just doesn't fit into the 2560a version of WindowGuiImpl.processEvent(). Is that correct?

I think the location in TopLevelWindow is alright. Why do you think it doesn't fit? Also should these events be generated by the swing driver as well?

  • There is a conflicting change in delimiter processing for ZeroColumnLayout.processStacked(), not sure if the rebased version is correct.

Looks like the conflicting code is in the way frame id is resolved. This should be straightforward.

  • I had to change usage of Window.resolveWindow() in both MessageAreaGuiImpl.doLayout() and ToolTip.hide(). I'm not sure if the change is correct.

Yes, this is correct. Window.resolveWindow() only moved to WindowManager, but its semantics didn't change.

I will create 1811t and do the changes there.

#1539 Updated by Hynek Cihlar over 8 years ago

Hynek Cihlar wrote:

I will create 1811t and do the changes there.

I will need to branch from 1811s, but since create_task_branch.sh doesn't allow this directly I will have to do it manually.

According to create_task_branch.sh, the correct commands should be:

bzr branch --no-tree /opt/secure/code/p2j_repo/p2j/active/1811s /opt/secure/code/p2j_repo/p2j/active/1811t
sudo /opt/scripts/fixup_task_branch_perms.sh 1811t

I won't be able to sudo though.

#1540 Updated by Greg Shah over 8 years ago

I will create 1811t and do the changes there.

There is no need.

When I said "review in a new branch", I really meant for you to just check out a new instance of 1811s and do the review there, while leaving your old copy of 1811s intact for comparison purposes.

You can make the edits directly in 1811s and check them in.

#1541 Updated by Hynek Cihlar over 8 years ago

Resolved rebase conflicts in revision 10995.

#1542 Updated by Greg Shah over 8 years ago

OK, people can update from 1811s now. It is "unlocked". Please note there is at least 1 regression (see #2763), the 3D borders on windows no longer draw properly.

#1543 Updated by Hynek Cihlar over 8 years ago

Constantin, I have a bit of a problem with batch-drawing modal windows. In ModalWindow.draw() I need to enable batch to render the window "at once" using GuiDriver.startBatch()/GuiDriver.endBatch(). When batch-mode is enabled however, the same thread is not allowed to select different window which happens when for example AbstractWidget.getTextWidth() is called. AbstractWidget.getTextWidth() uses the main window to resolve font metrics. I could probably make AbstractWidget.getTextWidth() to work with the actual top-level window but I think it would be better to somehow allow the switch at the driver level.

#1544 Updated by Constantin Asofiei over 8 years ago

Hynek Cihlar wrote:

Constantin, I have a bit of a problem with batch-drawing modal windows. In ModalWindow.draw() I need to enable batch to render the window "at once" using GuiDriver.startBatch()/GuiDriver.endBatch(). When batch-mode is enabled however, the same thread is not allowed to select different window which happens when for example AbstractWidget.getTextWidth() is called.

What is the stacktrace when ModalWindow.draw() is called? Is WindowGuiImpl.draw() called first, for the "parent" window? If so, I don't think this is correct... the ModalWindow should draw on its own. Maybe bracket ModalWindow.draw() in GuiDriver.saveDrawingState() and restoreDrawingState()?

AbstractWidget.getTextWidth() uses the main window to resolve font metrics.

This uses whatever window the current widget is attached to. So, for ModalWindow, what does window() return? You may want to override either window() or getTextWidth() in your ModalWindow implementation, but I'm not sure if this will not break something else.

#1545 Updated by Hynek Cihlar over 8 years ago

Constantin Asofiei wrote:

Hynek Cihlar wrote:

Constantin, I have a bit of a problem with batch-drawing modal windows. In ModalWindow.draw() I need to enable batch to render the window "at once" using GuiDriver.startBatch()/GuiDriver.endBatch(). When batch-mode is enabled however, the same thread is not allowed to select different window which happens when for example AbstractWidget.getTextWidth() is called.

What is the stacktrace when ModalWindow.draw() is called? Is WindowGuiImpl.draw() called first, for the "parent" window? If so, I don't think this is correct... the ModalWindow should draw on its own.

Yes, the modal window is drawn on its own.

FrameWindowGuiImpl.draw() line: 160    
GuiOutputManager(OutputManager<P>).setInvalidate(Widget<?>, boolean, boolean) line: 1275    
ThinClient.eventDrawingBracket(Widget<?>, boolean, boolean, Runnable) line: 13644    
ThinClient.eventDrawingBracket(Widget<?>, Runnable) line: 13543    
ThinClient.viewWorker(Widget, ScreenBuffer, int[], boolean, int) line: 9480    
ThinClient.view_(int, ScreenBuffer, int[], boolean, boolean, int) line: 8576    
ThinClient.view(int, ScreenBuffer, int[], boolean, int) line: 8411    
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]    
NativeMethodAccessorImpl.invoke(Object, Object[]) line: 62    
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 43    
Method.invoke(Object, Object...) line: 497    
MethodInvoker.invoke(Object[]) line: 76    
Dispatcher.processInbound(InboundRequest, boolean, NetResource) line: 705    

AbstractWidget.getTextWidth() uses the main window to resolve font metrics.

This uses whatever window the current widget is attached to. So, for ModalWindow, what does window() return?

By main window I meant the main window the widget is ultimately attached to. I.e. through the TopLevelWindow.owner field.

Please see revision 11000 for the fix.

#1546 Updated by Hynek Cihlar over 8 years ago

There is a new regression on GUI ALERT-BOX, it abends before it is displayed.

The cause is in Button.ctor() calling width(), this eventually causes AbstractWidget.getTextWidthNative() to be executed before the button is attached to the widget tree and then AbstractWidget.getTextWidthNative() fails to resolve its top-level window.

Here's the callstack:

Thread [main] (Suspended (exception RuntimeException))    
    owns: SwingGuiDriver  (id=267)    
    SwingGuiDriver$2(GuiPrimitivesImpl).getWindowEmulator(int) line: 208    
    SwingGuiDriver$2(GuiPrimitivesImpl).selectWindow(int) line: 275    
    SwingGuiDriver(AbstractGuiDriver<F>).selectWindow(int) line: 1624    
    ButtonGuiImpl(AbstractWidget<O>).getTextWidth(String, int, boolean) line: 2201    
    ButtonGuiImpl(AbstractWidget<O>).getTextWidthNative(String, int) line: 1787    
    ButtonGuiImpl.width() line: 1254    
    ButtonGuiImpl(Button<O>).<init>(WidgetId, Container<O>, String) line: 193    
    ButtonGuiImpl.<init>(WidgetId, Container<GuiOutputManager>, String) line: 145    
    GuiWidgetFactory.createButton(WidgetId, Container<GuiOutputManager>, String) line: 295    
    GuiWidgetFactory.createButton(WidgetId, Container, String) line: 1    
    AlertBoxCommons<O>.createButton(String) line: 379    
    AlertBoxCommons<O>.createButtonSet() line: 173    
    AlertBoxGuiImpl.init() line: 274    
    AlertBoxGuiImpl.<init>(int, Object[], int, logical, String) line: 118    
    GuiWidgetFactory.createAlertBox(int, Object[], int, logical, String, Color) line: 324    
    GuiWidgetFactory.createAlertBox(int, Object[], int, logical, String, Color) line: 1    
    ThinClient$12.run() line: 6717    
    ThinClient.eventBracket(boolean, Runnable) line: 13696    
    ThinClient.eventBracket(Runnable) line: 13669    
    ThinClient.messageBox(Object[], logical, int, int, String, Color, int) line: 6711    
    NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]    
    NativeMethodAccessorImpl.invoke(Object, Object[]) line: 62    
    DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 43    
    Method.invoke(Object, Object...) line: 497    
    MethodInvoker.invoke(Object[]) line: 76    
    Dispatcher.processInbound(InboundRequest, boolean, NetResource) line: 705    
    Conversation.block() line: 319    
    Conversation.waitMessage(int) line: 257    
    Queue.transactImpl(Message, int) line: 1128    
    Queue.transact(Message, int) line: 585    
    DirectSession(BaseSession).transact(Message, int) line: 223    
    HighLevelObject.transact(RoutingKey, Object[]) line: 163    
    RemoteObject$RemoteAccess.invokeCore(Object, Method, Object[]) line: 1425    
    RemoteObject$RemoteAccess(InvocationStub).invoke(Object, Method, Object[]) line: 97    
    $Proxy4.standardEntry(ClientParameters) line: not available    
    ClientCore.start(BootstrapConfig, boolean, boolean) line: 277    
    ClientCore.start(BootstrapConfig, boolean) line: 100    
    ClientDriver.start(BootstrapConfig) line: 201    
    ClientDriver(CommonDriver).process(String[]) line: 398    
    ClientDriver.process(String[]) line: 1    
    ClientDriver.main(String[]) line: 267    

#1547 Updated by Hynek Cihlar over 8 years ago

Hynek Cihlar wrote:

There is a new regression on GUI ALERT-BOX, it abends before it is displayed.

Fixed in revision 11002. Please review.

#1548 Updated by Constantin Asofiei over 8 years ago

Hynek Cihlar wrote:

By main window I meant the main window the widget is ultimately attached to. I.e. through the TopLevelWindow.owner field.

Please see revision 11000 for the fix.

Do you think there can be an usecase where topWindow and window vars will be different?

#1549 Updated by Hynek Cihlar over 8 years ago

Constantin Asofiei wrote:

Hynek Cihlar wrote:

By main window I meant the main window the widget is ultimately attached to. I.e. through the TopLevelWindow.owner field.

Please see revision 11000 for the fix.

Do you think there can be an usecase where topWindow and window vars will be different?

Yes, widget in a dialog-box or alert-box. topWindow will point ot the modal window, and window will point to the main window. The main window is needed to resolve font details while top-level window for GUI output.

#1550 Updated by Greg Shah over 8 years ago

Rebased task branch 1811s from P2J trunk revision 10949. The current revision in 1811s is now 11009.

DO NOT pull down these changes yet.

Stanislav: I found an issue in the ScrollPaneGuiImpl.adjustScrollBar() during the merge. It was missing a closing curly brace. I placed one in the location that seems correct, but it needs your review. Search on SVL-REBASE to find it. Make any changes needed and then delete the marker. Once you have checked in, we will consider the branch unlocked.

#1551 Updated by Stanislav Lomany over 8 years ago

Done (rev 11010).

#1552 Updated by Hynek Cihlar over 8 years ago

There seems to be a new regression. PAUSE as the first instruction doesn't cause the main window to be shown. Tested on 1811s revision 11013.

#1553 Updated by Constantin Asofiei over 8 years ago

Hynek Cihlar wrote:

There seems to be a new regression. PAUSE as the first instruction doesn't cause the main window to be shown. Tested on 1811s revision 11013.

The DEFAULT-WINDOW is made visible only when something is output on it. PAUSE (and I suspect STATUS, too) need a code like this (from WindowGuiImpl.message:1936):

      if (!isVisible())
      {
         ThinClient.getInstance().independentEventDrawingBracket(this, new Runnable()
         {
            public void run()
            {
               show();
            }
         });
      }

#1554 Updated by Hynek Cihlar over 8 years ago

Constantin, can you please review 1811s 11018? This is a small change in widget focusing and resolves buttons focus in ALERT-BOX. This could have greater impact though. Thanks.

#1555 Updated by Constantin Asofiei over 8 years ago

Hynek Cihlar wrote:

Constantin, can you please review 1811s 11018? This is a small change in widget focusing and resolves buttons focus in ALERT-BOX. This could have greater impact though. Thanks.

I don't think I understand the change - are you trying to not alter the 4GL-level focused widget for ALERT-BOX? In 4GL terms, "gaining focus" really means the widget is currently focused and all events are set to it, so discarding the requestFocus call will affect this. So, what happens if you press the space key just after the ALERT-BOX was shown? Is the key navigation for buttons still working?

But, if the 4GL-level focus is restored back to the widget which had focus before the ALERT-BOX was shown, then I think is OK.

#1556 Updated by Hynek Cihlar over 8 years ago

Constantin Asofiei wrote:

Hynek Cihlar wrote:

Constantin, can you please review 1811s 11018? This is a small change in widget focusing and resolves buttons focus in ALERT-BOX. This could have greater impact though. Thanks.

I don't think I understand the change - are you trying to not alter the 4GL-level focused widget for ALERT-BOX? In 4GL terms, "gaining focus" really means the widget is currently focused and all events are set to it,

I interpret "gaining focus" as gaining focus actually :-). That is, the initial state is widget has no focus and the "gaining focus" operation will highlight the subjected widget and move focus to it. Are we talking about the same thing?

so discarding the requestFocus call will affect this. So, what happens if you press the space key just after the ALERT-BOX was shown? Is the key navigation for buttons still working?

But, if the 4GL-level focus is restored back to the widget which had focus before the ALERT-BOX was shown, then I think is OK.

I really added the call to requestFocus(). Before this change, when you pressed TAB in ALERT-BOX the next button in the tab sequence was highlighted but the focus really stayed on the original widget. I traced the focus logic for this case and found two places where focus is being changed, FocusManager (moves focus forward and back, probably to handle associated 4GL eventing) and the final block of ThinClient.processEventsWorker().

#1557 Updated by Greg Shah over 8 years ago

The key processing for alert-box seems broken right now (rev 11020). Tab does not switch buttons. Space does not select a button.

In addition, although I know our focus processing implementation is sub-optimal, the way I understand things is that the LEAVE/ENTRY processing will put the focus back on the target (new) widget if the old/new widgets don't cancel the focus change using a NO-APPLY trigger. There is not supposed to be a reason to force the requestFocus() where you have added it. I worry that this is going to have negative consequences.

#1558 Updated by Greg Shah over 8 years ago

Code Review Task Branch 1811s Revision 11025

The code is pretty clean. I have checked in some minor formatting and comment cleanup as revision 11026. Here are the other items:

1. In GenericWidget we have this history entry but no other changes:

** 096 CA  20151016          Fixed NPE when LABEL attr for dyn widget is set before FRAME.

Did we lose something in the rebase or otherwise?

2. MenuChuiImpl, WindowChuiImpl are missing their history entries.

Vadim, please fix it.

3. MenuItemChuiImpl has non-wildcard imports.

Vadim, please fix it.

4. In p2j.canvas_renderer.js there is this code:

   if (text.startsWith("chcbf2:"))
   {
      console.log(text + this.origin.x + " " + this.origin.y + " x:" + x + "y: " +y);
      var lastClip = this.lastClippedRegion(true);
      console.log(text + lastClip.y + " " + lastClip.x + " " + lastClip.height + " " + lastClip.width); 
   }

Sergey: Is it still needed?

#1559 Updated by Sergey Ivanovskiy over 8 years ago

Code Review Task Branch 1811s Revision 11025
4. In p2j.canvas_renderer.js there is this code:

if (text.startsWith("chcbf2:"))
   {
      console.log(text + this.origin.x + " " + this.origin.y + " x:" + x + "y: " +y);
      var lastClip = this.lastClippedRegion(true);
      console.log(text + lastClip.y + " " + lastClip.x + " " + lastClip.height + " " + lastClip.width); 
   }

Sergey: Is it still needed?

Yes, it can be removed.

#1560 Updated by Constantin Asofiei over 8 years ago

Guys, has anyone done changes related to window Insert mode, after 1811s rev 11014? I have regressions in ChUI related to this.

#1561 Updated by Sergey Ivanovskiy over 8 years ago

Constantin Asofiei wrote:

Guys, has anyone done changes related to window Insert mode, after 1811s rev 11014? I have regressions in ChUI related to this.

What is a regression?

#1562 Updated by Constantin Asofiei over 8 years ago

Greg Shah wrote:

The key processing for alert-box seems broken right now (rev 11020). Tab does not switch buttons. Space does not select a button.

In addition, although I know our focus processing implementation is sub-optimal, the way I understand things is that the LEAVE/ENTRY processing will put the focus back on the target (new) widget if the old/new widgets don't cancel the focus change using a NO-APPLY trigger. There is not supposed to be a reason to force the requestFocus() where you have added it. I worry that this is going to have negative consequences.

Your worries were valid, GSO 53 (and others) failed with insert mode, as it got changed when the widget requested focus... I'm backing out this change and running main part again.

Conversion testing passed with 1811s rev 11027.

#1563 Updated by Hynek Cihlar over 8 years ago

Constantin Asofiei wrote:

Greg Shah wrote:

The key processing for alert-box seems broken right now (rev 11020). Tab does not switch buttons. Space does not select a button.

In addition, although I know our focus processing implementation is sub-optimal, the way I understand things is that the LEAVE/ENTRY processing will put the focus back on the target (new) widget if the old/new widgets don't cancel the focus change using a NO-APPLY trigger. There is not supposed to be a reason to force the requestFocus() where you have added it. I worry that this is going to have negative consequences.

Your worries were valid, GSO 53 (and others) failed with insert mode, as it got changed when the widget requested focus... I'm backing out this change and running main part again.

I am working on a better solution.

#1564 Updated by Greg Shah over 8 years ago

Sergey: Is it still needed?

Yes, it can be removed.

Please remove it from 1811s.

#1565 Updated by Sergey Ivanovskiy over 8 years ago

Committed revision 11031 removes the debug code.

#1566 Updated by Greg Shah over 8 years ago

I am working on a better solution.

We may not be able to check it into 1811s.

Please provide detail on why you made this change originally.

#1567 Updated by Hynek Cihlar over 8 years ago

Greg Shah wrote:

I am working on a better solution.

We may not be able to check it into 1811s.

Please provide detail on why you made this change originally.

The change is to fix focusing of ALERT-BOX buttons when TAB or SHIFT-TAB is pressed. When TAB is pressed the focus won't change from button A to button B, only highlight changes to button B.

#1568 Updated by Greg Shah over 8 years ago

The change is to fix focusing of ALERT-BOX buttons when TAB or SHIFT-TAB is pressed. When TAB is pressed the focus won't change from button A to button B, only highlight changes to button B.

Is ALERT-BOX TAB/SHIFT-TAB processing broken in ChUI too?

#1569 Updated by Hynek Cihlar over 8 years ago

Greg Shah wrote:

The change is to fix focusing of ALERT-BOX buttons when TAB or SHIFT-TAB is pressed. When TAB is pressed the focus won't change from button A to button B, only highlight changes to button B.

Is ALERT-BOX TAB/SHIFT-TAB processing broken in ChUI too?

No, ChUI works fine.

#1570 Updated by Sergey Ivanovskiy over 8 years ago

./ask-gui.p if "Program name" is filled with misspelled program name, then the swing/web client fails and swing client restarts unresponsive
The log for swing client contains this exception

Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
    at com.goldencode.p2j.ui.client.gui.driver.swing.SwingEmulatedWindow$3.windowLostFocus(SwingEmulatedWindow.java:269)
    at java.awt.Window.processWindowFocusEvent(Window.java:2108)
    at java.awt.Window.processEvent(Window.java:2021)
    at java.awt.Component.dispatchEventImpl(Component.java:4881)
    at java.awt.Container.dispatchEventImpl(Container.java:2292)
    at java.awt.Window.dispatchEventImpl(Window.java:2750)
    at java.awt.Component.dispatchEvent(Component.java:4703)
    at java.awt.KeyboardFocusManager.redispatchEvent(KeyboardFocusManager.java:1954)
    at java.awt.DefaultKeyboardFocusManager.typeAheadAssertions(DefaultKeyboardFocusManager.java:995)
    at java.awt.DefaultKeyboardFocusManager.dispatchEvent(DefaultKeyboardFocusManager.java:749)
    at java.awt.Component.dispatchEventImpl(Component.java:4752)
    at java.awt.Container.dispatchEventImpl(Container.java:2292)
    at java.awt.Window.dispatchEventImpl(Window.java:2750)
    at java.awt.Component.dispatchEvent(Component.java:4703)
    at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:758)
    at java.awt.EventQueue.access$500(EventQueue.java:97)
    at java.awt.EventQueue$3.run(EventQueue.java:709)
    at java.awt.EventQueue$3.run(EventQueue.java:703)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:75)
    at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:86)
    at java.awt.EventQueue$4.run(EventQueue.java:731)
    at java.awt.EventQueue$4.run(EventQueue.java:729)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:75)
    at java.awt.EventQueue.dispatchEvent(EventQueue.java:728)
    at java.awt.SequencedEvent.dispatch(SequencedEvent.java:128)
    at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:756)
    at java.awt.EventQueue.access$500(EventQueue.java:97)
    at java.awt.EventQueue$3.run(EventQueue.java:709)
    at java.awt.EventQueue$3.run(EventQueue.java:703)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:75)
    at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:86)
    at java.awt.EventQueue$4.run(EventQueue.java:731)
    at java.awt.EventQueue$4.run(EventQueue.java:729)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:75)
    at java.awt.EventQueue.dispatchEvent(EventQueue.java:728)
    at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:201)
    at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116)
    at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105)
    at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
    at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93)
    at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)

#1571 Updated by Sergey Ivanovskiy over 8 years ago

./demo/calc.p com.goldencode.testcases.demo.Calc.execute
passed The calc window looks the same as for the swing client
./demo/calc-static.p com.goldencode.testcases.demo.CalcStatic.execute
passed The calc window looks the same as for the swing client

./demo/calc-static-chars.p com.goldencode.testcases.demo.CalcStaticChars.execute
worked with drawing artifacts: the scroll bars are present and the buttons layout is changed if buttons are pressed

./demo/calc-static-pixels.p com.goldencode.testcases.demo.CalcStaticPixels.execute
The calculator is opened with broken input fields border from the right side.

#1572 Updated by Greg Shah over 8 years ago

Task branch 1811s has passed regression testing (conversion, main runtime and CTRL-C).

Merged to trunk as revision 10950.

Archived branch 1811s.

#1573 Updated by Constantin Asofiei over 8 years ago

Hynek, if you haven't figure out already, I think the GUI client doesn't even process the TAB key. Look at this code in AbstractButton.processKeyEventCommon:

      if (parent() instanceof AlertBox)
      {
         setActionCode(keyEvent);
      }
      else
      {
         if (handleCursorKeys(keyEvent.actionCode(), (Frame<O>) UiUtils.locateFrame(this)))
            return;
      }

In both ChUI and GUI, the source is a BUTTON. In the ChUI case, the button is attached directly to an AlertBox. In a GUI case, the button is attached to a AlertBoxGuiImpl$ButtonsPanel - thus it will go through the else branch and just return, as there is no frame in the alert-box case.

Changing the code to this:

      if (parentOrSelf(AlertBox.class) != null)
      {
         setActionCode(keyEvent);
      }
      else
      {
         if (handleCursorKeys(keyEvent.actionCode(), (Frame<O>) UiUtils.locateFrame(this)))
            return;
      }

fixes the GUI.

I guess we should do a review of all instanceof cases and replace them with parentOrSelf calls?

#1574 Updated by Hynek Cihlar over 8 years ago

Constantin Asofiei wrote:

Hynek, if you haven't figure out already, I think the GUI client doesn't even process the TAB key. Look at this code in AbstractButton.processKeyEventCommon:
[...]
In both ChUI and GUI, the source is a BUTTON. In the ChUI case, the button is attached directly to an AlertBox. In a GUI case, the button is attached to a AlertBoxGuiImpl$ButtonsPanel - thus it will go through the else branch and just return, as there is no frame in the alert-box case.

Changing the code to this:
[...]
fixes the GUI.

I guess we should do a review of all instanceof cases and replace them with parentOrSelf calls?

Constantin, thanks for taking the time to analyze this. I am testing a fix already. Yes, I have found my way, spotted the widget tree issue, added other missing features like key shortcuts, etc. I hope you will like this fix better.

#1575 Updated by Constantin Asofiei over 8 years ago

Branch 2688a rev 10956 has (refs #2272):
  • Added support for WINDOW:SENSITIVE attribute.
  • Ignore WIDGET-ID option at frame definition (for now).
  • Widgets (non-container) with no mouse actions are ignored during findMouseSource (i.e. RECTANGLE).
  • Fixed frame's window resolution when CURRENT-WINDOW is actually the frame's window, and not the DEFAULT-WINDOW.

#1576 Updated by Greg Shah over 8 years ago

I haven't checked if this link is still relevant in today's browsers, but it does document a cross-browser approach to loading a custom cursor.

https://beradrian.wordpress.com/2008/01/08/cross-browser-custom-css-cursors/

We won't use a server-based URL. Instead we would want to implement the same way we did for custom fonts (using a data: URI). See p2j.fonts.js in the createFont() function.

#1577 Updated by Greg Shah over 8 years ago

Created task branch 1811t from P2J trunk 10956. This will contain the changes for #2844, #2906, #2912, #2916, #2672, #1774 and other fixes/improvements.

#1578 Updated by Constantin Asofiei over 8 years ago

Moved to #2272 note 405.

#1579 Updated by Constantin Asofiei over 8 years ago

  • File audcfsd0.w_user_defined_alerts.png added
  • File audcfsd0.w_user_defined_alerts_error.png added

Mode to #2272 note 406.

#1580 Updated by Constantin Asofiei over 8 years ago

Greg, the issues I'm seeing in note 1579 are related to DIALOG-BOX. The alert message is generated by the same code in note 1578.

Should I release 2795a and fix this in 1811t?

#1581 Updated by Hynek Cihlar over 8 years ago

Constantin Asofiei wrote:

Greg, the issues I'm seeing in note 1579 are related to DIALOG-BOX. The alert message is generated by the same code in note 1578.

Should I release 2795a and fix this in 1811t?

This is potentially solved by 2875a.

#1582 Updated by Constantin Asofiei over 8 years ago

  • File audcfsd0.w_user_defined_alerts_2875a.png added

Moved to #2272 note 407.

#1583 Updated by Hynek Cihlar over 8 years ago

Constantin Asofiei wrote:

Hynek Cihlar wrote:

Constantin Asofiei wrote:

Greg, the issues I'm seeing in note 1579 are related to DIALOG-BOX. The alert message is generated by the same code in note 1578.

Should I release 2795a and fix this in 1811t?

This is potentially solved by 2875a.

With 2795a + 2875a, the unresponsiveness issue is fixed, but the alert message is still shown; plus, the User Defined Alerts screen is now incorrect:

Also, there is lots of flickering during drawing, in all test 454 windows, after every click looks like the entire window is redrawn. The good news is the main test 454 window can be reached, and looks OK (except the flickering).

The flickering is indeed caused by 2875a. But I am surprised by the visible scroll bars. I am looking into both.

#1584 Updated by Constantin Asofiei over 8 years ago

Moved to #2272 note 408

#1585 Updated by Constantin Asofiei over 8 years ago

  • File deleted (audcfsd0.w_user_defined_alerts.png)

#1586 Updated by Constantin Asofiei over 8 years ago

  • File deleted (audcfsd0.w_user_defined_alerts_error.png)

#1587 Updated by Constantin Asofiei over 8 years ago

  • File deleted (audcfsd0.w_user_defined_alerts_2875a.png)

#1588 Updated by Constantin Asofiei over 8 years ago

I've moved notes 1578, 1579, 1582 and 1584 to #2272 as they contain customer specific details. Let's continue any more discussion there.

#1589 Updated by Greg Shah about 8 years ago

Rebased 1811t from trunk revision 10970. The latest revision is now 11018.

#1590 Updated by Greg Shah about 8 years ago

Constantin: in note 1087, is the issue listed as performance lag between pressing keys and seeing results drawn in web client still a problem?

#1591 Updated by Constantin Asofiei about 8 years ago

Greg Shah wrote:

Constantin: in note 1087, is the issue listed as performance lag between pressing keys and seeing results drawn in web client still a problem?

No, is improved now. It was fixed via 1. the drawing optimization and 2. the explicit EDITOR optimizations.

#1592 Updated by Greg Shah about 8 years ago

  • Status changed from WIP to Closed

I'm closing this task, since the core web client is not only implemented, it is working very well.

All remaining work is defined in other tasks.

#1593 Updated by Greg Shah almost 8 years ago

1811u has passed conversion testing and main regression testing. CTRL-C testing is in progress now.

Manual GUI regression testing is mostly complete, no regressions are known so far.

#1594 Updated by Greg Shah almost 8 years ago

CTRL-C testing completed successfully for 1811u. Manual GUI testing completed successfully. 1811u will be merged to trunk.

#1595 Updated by Greg Shah almost 8 years ago

1811u has been merged to trunk as revision 11013. The task branch has been archived.

#1596 Updated by Greg Shah over 7 years ago

  • Target version changed from Milestone 12 to GUI Support for a Complex ADM2 App

#1597 Updated by Greg Shah about 7 years ago

  • Related to Bug #3250: win_spawn failure cannot create process added

Also available in: Atom PDF