deploy_dialog.vala 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. /*
  2. * Copyright (c) 2012-2025 Daniele Bartolini et al.
  3. * SPDX-License-Identifier: GPL-3.0-or-later
  4. */
  5. using Gtk;
  6. using Gee;
  7. namespace Crown
  8. {
  9. public ComboBoxMap make_deploy_config_combo()
  10. {
  11. string? labels[] =
  12. {
  13. TargetConfig.RELEASE.to_label(),
  14. TargetConfig.DEVELOPMENT.to_label(),
  15. #if CROWN_DEBUG
  16. TargetConfig.DEBUG.to_label(),
  17. #endif
  18. };
  19. string? ids[] =
  20. {
  21. ((int)TargetConfig.RELEASE).to_string(),
  22. ((int)TargetConfig.DEVELOPMENT).to_string(),
  23. #if CROWN_DEBUG
  24. ((int)TargetConfig.DEBUG).to_string(),
  25. #endif
  26. };
  27. return new ComboBoxMap(0, labels, ids);
  28. }
  29. public Gtk.Button make_deploy_button(TargetPlatform platform)
  30. {
  31. var btn = new Gtk.Button();
  32. btn.label = "Package Project for %s".printf(platform.to_label());
  33. btn.margin_start = 12;
  34. btn.margin_end = 12;
  35. btn.margin_top = 12;
  36. btn.get_style_context().add_class(Gtk.STYLE_CLASS_SUGGESTED_ACTION);
  37. return btn;
  38. }
  39. public delegate int DeployerCheckConfig();
  40. public class DeployerPage : Gtk.Bin
  41. {
  42. public Gtk.Box _check_config_box;
  43. public Gtk.Widget _deployer_options;
  44. public Gtk.Stack _stack;
  45. public unowned DeployerCheckConfig _check_config;
  46. public DeployerPage(TargetPlatform target_platform, Gtk.Widget deployer_options, DeployerCheckConfig? check_config = null)
  47. {
  48. _deployer_options = deployer_options;
  49. _check_config = check_config;
  50. string h1 = "<span font_weight=\"bold\" size=\"x-large\">Not configured</span>";
  51. string p1 = "The %s deployer has not been configured yet".printf(target_platform.to_label());
  52. string p2 = "Follow <a href=\"\">the instructions</a> to get started";
  53. var h1l = new Gtk.Label(null);
  54. h1l.set_markup(h1);
  55. h1l.valign = Gtk.Align.CENTER;
  56. var p1l = new Gtk.Label(null);
  57. p1l.set_markup(p1);
  58. p1l.valign = Gtk.Align.CENTER;
  59. var p2l = new Gtk.Label(null);
  60. p2l.track_visited_links = false;
  61. p2l.set_markup(p2);
  62. p2l.valign = Gtk.Align.CENTER;
  63. p2l.activate_link.connect(() => {
  64. try {
  65. string CROWN_DEPLOY_URL = CROWN_LATEST_DOCS_URL + "/deploying";
  66. string CROWN_DEPLOY_CONFIG_URL = CROWN_DEPLOY_URL + "/%s.html".printf(target_platform.to_key());
  67. AppInfo.launch_default_for_uri(CROWN_DEPLOY_CONFIG_URL, null);
  68. } catch (Error e) {
  69. loge(e.message);
  70. }
  71. return true;
  72. });
  73. _check_config_box = new Gtk.Box(Gtk.Orientation.VERTICAL, 12);
  74. _check_config_box.valign = Gtk.Align.CENTER;
  75. _check_config_box.pack_start(h1l);
  76. _check_config_box.pack_start(p1l);
  77. _check_config_box.pack_start(p2l);
  78. _stack = new Gtk.Stack();
  79. _stack.add(_check_config_box);
  80. _stack.add(_deployer_options);
  81. this.map.connect(on_map);
  82. this.add(_stack);
  83. }
  84. private void on_map()
  85. {
  86. if (_check_config != null) {
  87. if (_check_config() != 0)
  88. _stack.set_visible_child(_check_config_box);
  89. else
  90. _stack.set_visible_child(_deployer_options);
  91. } else {
  92. _stack.set_visible_child(_deployer_options);
  93. }
  94. }
  95. }
  96. #if CROWN_PLATFORM_WINDOWS
  97. public bool can_build_32bit_arm = false;
  98. #else
  99. public bool can_build_32bit_arm = true;
  100. #endif
  101. public class DeployDialog : Gtk.Dialog
  102. {
  103. public RuntimeInstance _editor;
  104. public Project _project;
  105. // Android page.
  106. public Gtk.Button _android_deploy_button;
  107. public Gtk.FileChooserButton _android_output_path;
  108. public ComboBoxMap _android_config;
  109. public CheckBox _android_armv7;
  110. public CheckBox _android_armv8;
  111. public CheckBox _android_use_debug_keystore;
  112. public Gtk.FileChooserButton _android_keystore;
  113. public Gtk.Entry _android_keystore_password;
  114. public Gtk.Entry _android_key_alias;
  115. public Gtk.Entry _android_key_password;
  116. public Gtk.Entry _android_app_title;
  117. public Gtk.Entry _android_app_identifier;
  118. public Gtk.Entry _android_app_version_code;
  119. public Gtk.Entry _android_app_version_name;
  120. public PropertyGridSet _android_set;
  121. public Gtk.Box _android_box;
  122. public AndroidDeployer _android;
  123. public DeployerPage _android_page;
  124. // HTML5 page.
  125. public Gtk.Button _html5_deploy_button;
  126. public Gtk.FileChooserButton _html5_output_path;
  127. public ComboBoxMap _html5_config;
  128. public Gtk.Entry _html5_app_title;
  129. public PropertyGridSet _html5_set;
  130. public Gtk.Box _html5_box;
  131. public HTML5Deployer _html5;
  132. public DeployerPage _html5_page;
  133. // Linux page.
  134. public Gtk.Button _linux_deploy_button;
  135. public Gtk.FileChooserButton _linux_output_path;
  136. public ComboBoxMap _linux_config;
  137. public Gtk.Entry _linux_app_title;
  138. public PropertyGridSet _linux_set;
  139. public Gtk.Box _linux_box;
  140. public DeployerPage _linux_page;
  141. // Windows page.
  142. public Gtk.Button _windows_deploy_button;
  143. public Gtk.FileChooserButton _windows_output_path;
  144. public ComboBoxMap _windows_config;
  145. public EntryText _windows_app_title;
  146. public PropertyGridSet _windows_set;
  147. public Gtk.Box _windows_box;
  148. public DeployerPage _windows_page;
  149. public Gtk.Notebook _notebook;
  150. public DeployDialog(Project project, RuntimeInstance editor)
  151. {
  152. this.title = "Deploy";
  153. this.border_width = 0;
  154. this.set_icon_name(CROWN_EDITOR_ICON_NAME);
  155. _project = project;
  156. _editor = editor;
  157. // Android page.
  158. _android_deploy_button = make_deploy_button(TargetPlatform.ANDROID);
  159. _android_deploy_button.clicked.connect(() => {
  160. // Validate input fields.
  161. string? output_path = _android_output_path.get_filename();
  162. if (output_path == null) {
  163. loge("Select a valid output Destination");
  164. return;
  165. }
  166. string app_title = _android_app_title.text.strip();
  167. if (app_title.length == 0) {
  168. loge("Enter a valid App Title");
  169. return;
  170. }
  171. string app_identifier = _android_app_identifier.text.strip();
  172. if (app_title.length == 0 || app_identifier.split(".").length != 3) {
  173. loge("Enter a valid App Identifier");
  174. return;
  175. }
  176. int app_version_code;
  177. if (int.try_parse(_android_app_version_code.text, out app_version_code) == false) {
  178. loge("Enter a valid App Version Code");
  179. return;
  180. }
  181. string app_version_name = _android_app_version_name.text.strip();
  182. if (app_version_name.length == 0) {
  183. loge("Enter a valid App Version Name");
  184. return;
  185. }
  186. string? keystore_path = _android_use_debug_keystore.get_active()
  187. ? GLib.Path.build_filename(GLib.Environment.get_home_dir(), ".android", "debug.keystore")
  188. : _android_keystore.get_filename()
  189. ;
  190. if (keystore_path == null) {
  191. loge("Enter a valid Keystore file");
  192. return;
  193. }
  194. string keystore_pass = _android_use_debug_keystore.get_active()
  195. ? "android"
  196. : _android_keystore_password.text
  197. ;
  198. if (keystore_path.length == 0) {
  199. loge("Enter a valid Keystore Password");
  200. return;
  201. }
  202. string key_alias = _android_use_debug_keystore.get_active()
  203. ? "androiddebugkey"
  204. : _android_key_alias.text
  205. ;
  206. if (key_alias.length == 0) {
  207. loge("Enter a valid Key Alias");
  208. return;
  209. }
  210. string key_pass = _android_use_debug_keystore.get_active()
  211. ? "android"
  212. : _android_key_password.text
  213. ;
  214. if (key_pass.length == 0) {
  215. loge("Enter a valid Key Password");
  216. return;
  217. }
  218. TargetArch[] archs =
  219. {
  220. TargetArch.ARM,
  221. TargetArch.ARM64
  222. };
  223. for (int ii = 0; ii < archs.length; ++ii) {
  224. if (archs[ii] == TargetArch.ARM && !_android_armv7.value)
  225. continue;
  226. if (archs[ii] == TargetArch.ARM64 && !_android_armv8.value)
  227. continue;
  228. // Create the package.
  229. GLib.Variant paramz[] =
  230. {
  231. (string)output_path,
  232. int.parse(_android_config.get_active_id()),
  233. app_title,
  234. app_identifier,
  235. app_version_code,
  236. app_version_name,
  237. keystore_path,
  238. keystore_pass,
  239. key_alias,
  240. key_pass,
  241. archs[ii]
  242. };
  243. GLib.Application.get_default().activate_action("create-package-android"
  244. , new GLib.Variant.tuple(paramz));
  245. }
  246. });
  247. _android_output_path = new Gtk.FileChooserButton("Select folder", Gtk.FileChooserAction.SELECT_FOLDER);
  248. _android_config = make_deploy_config_combo();
  249. _android_armv7 = new CheckBox();
  250. _android_armv7.value = false;
  251. _android_armv8 = new CheckBox();
  252. _android_armv8.value = true;
  253. _android_use_debug_keystore = new CheckBox();
  254. _android_use_debug_keystore.value_changed.connect(() => { android_set_debug_keystore(); });
  255. _android_use_debug_keystore.value = true;
  256. _android_keystore = new Gtk.FileChooserButton("Select file", Gtk.FileChooserAction.OPEN);
  257. _android_keystore_password = new Gtk.Entry();
  258. _android_keystore_password.set_visibility(false);
  259. _android_keystore_password.input_purpose = Gtk.InputPurpose.PASSWORD;
  260. _android_key_alias = new Gtk.Entry();
  261. _android_key_password = new Gtk.Entry();
  262. _android_key_password.set_visibility(false);
  263. _android_key_password.input_purpose = Gtk.InputPurpose.PASSWORD;
  264. _android_app_title = new EntryText();
  265. _android_app_title.placeholder_text = "My Application";
  266. _android_app_title.text = _project.name();
  267. _android_app_identifier = new Gtk.Entry();
  268. _android_app_identifier.placeholder_text = "org.company.product";
  269. _android_app_version_code = new Gtk.Entry();
  270. _android_app_version_code.input_purpose = Gtk.InputPurpose.DIGITS;
  271. _android_app_version_code.placeholder_text = "1";
  272. _android_app_version_name = new Gtk.Entry();
  273. _android_app_version_name.placeholder_text = "1.0";
  274. android_set_debug_keystore();
  275. _android_set = new PropertyGridSet();
  276. _android_set.border_width = 12;
  277. // Android Output grid.
  278. PropertyGrid cv;
  279. cv = new PropertyGrid();
  280. cv.column_homogeneous = true;
  281. cv.add_row("Destination", _android_output_path);
  282. cv.add_row("Config", _android_config);
  283. cv.add_row("ARMv7-A", _android_armv7).sensitive = can_build_32bit_arm;
  284. _android_armv7.sensitive = can_build_32bit_arm;
  285. cv.add_row("ARMv8-A", _android_armv8);
  286. _android_set.add_property_grid(cv, "Output");
  287. // Android Application.
  288. cv = new PropertyGrid();
  289. cv.column_homogeneous = true;
  290. cv.add_row("Title", _android_app_title);
  291. cv.add_row("Identifier", _android_app_identifier);
  292. cv.add_row("Version Code", _android_app_version_code);
  293. cv.add_row("Version Name", _android_app_version_name);
  294. _android_set.add_property_grid(cv, "Application");
  295. // Android Signing.
  296. cv = new PropertyGrid();
  297. cv.column_homogeneous = true;
  298. cv.add_row("Use debug keystore", _android_use_debug_keystore);
  299. cv.add_row("Keystore", _android_keystore);
  300. cv.add_row("Keystore password", _android_keystore_password);
  301. cv.add_row("Alias", _android_key_alias);
  302. cv.add_row("Key password", _android_key_password);
  303. _android_set.add_property_grid(cv, "Signing");
  304. // Android box.
  305. _android_box = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
  306. _android_box.pack_start(_android_deploy_button, false, true, 0);
  307. _android_box.pack_start(_android_set, false, true, 0);
  308. _android = new AndroidDeployer();
  309. _android_page = new DeployerPage(TargetPlatform.ANDROID, _android_box, _android.check_config);
  310. // HTML5 page.
  311. _html5_deploy_button = make_deploy_button(TargetPlatform.HTML5);
  312. _html5_deploy_button.clicked.connect(() => {
  313. // Validate input fields.
  314. string? output_path = _html5_output_path.get_filename();
  315. if (output_path == null) {
  316. loge("Select a valid output Destination");
  317. return;
  318. }
  319. string app_title = _html5_app_title.text.strip();
  320. if (app_title.length == 0) {
  321. loge("Enter a valid Title");
  322. return;
  323. }
  324. // Create the package.
  325. GLib.Variant paramz[] =
  326. {
  327. (string)output_path,
  328. int.parse(_html5_config.get_active_id()),
  329. app_title
  330. };
  331. GLib.Application.get_default().activate_action("create-package-html5"
  332. , new GLib.Variant.tuple(paramz));
  333. });
  334. _html5_output_path = new Gtk.FileChooserButton("Select folder", Gtk.FileChooserAction.SELECT_FOLDER);
  335. _html5_config = make_deploy_config_combo();
  336. _html5_app_title = new EntryText();
  337. _html5_app_title.placeholder_text = "My Application";
  338. _html5_app_title.text = _project.name();
  339. _html5_set = new PropertyGridSet();
  340. _html5_set.border_width = 12;
  341. // HTML5 box.
  342. _html5_box = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
  343. _html5_box.pack_start(_html5_deploy_button, false, true, 0);
  344. _html5_box.pack_start(_html5_set, false, true, 0);
  345. _html5 = new HTML5Deployer();
  346. _html5_page = new DeployerPage(TargetPlatform.HTML5, _html5_box, _html5.check_config);
  347. // HTML5 General page.
  348. cv = new PropertyGrid();
  349. cv.column_homogeneous = true;
  350. cv.add_row("Destination", _html5_output_path);
  351. cv.add_row("Config", _html5_config);
  352. _html5_set.add_property_grid(cv, "Output");
  353. // HTML5 Application.
  354. cv = new PropertyGrid();
  355. cv.column_homogeneous = true;
  356. cv.add_row("Title", _html5_app_title);
  357. _html5_set.add_property_grid(cv, "Application");
  358. // Linux page.
  359. _linux_deploy_button = make_deploy_button(TargetPlatform.LINUX);
  360. _linux_deploy_button.clicked.connect(() => {
  361. // Validate input fields.
  362. string? output_path = _linux_output_path.get_filename();
  363. if (output_path == null) {
  364. loge("Select a valid output Destination");
  365. return;
  366. }
  367. string app_title = _linux_app_title.text.strip();
  368. if (app_title.length == 0) {
  369. loge("Enter a valid Title");
  370. return;
  371. }
  372. // Create the package.
  373. GLib.Variant paramz[] =
  374. {
  375. (string)output_path,
  376. int.parse(_linux_config.get_active_id()),
  377. app_title
  378. };
  379. GLib.Application.get_default().activate_action("create-package-linux"
  380. , new GLib.Variant.tuple(paramz));
  381. });
  382. _linux_output_path = new Gtk.FileChooserButton("Select folder", Gtk.FileChooserAction.SELECT_FOLDER);
  383. _linux_config = make_deploy_config_combo();
  384. _linux_app_title = new EntryText();
  385. _linux_app_title.placeholder_text = "My Application";
  386. _linux_app_title.text = _project.name();
  387. _linux_set = new PropertyGridSet();
  388. _linux_set.border_width = 12;
  389. // Linux box.
  390. _linux_box = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
  391. _linux_box.pack_start(_linux_deploy_button, false, true, 0);
  392. _linux_box.pack_start(_linux_set, false, true, 0);
  393. _linux_page = new DeployerPage(TargetPlatform.LINUX, _linux_box);
  394. // Linux General page.
  395. cv = new PropertyGrid();
  396. cv.column_homogeneous = true;
  397. cv.add_row("Destination", _linux_output_path);
  398. cv.add_row("Config", _linux_config);
  399. _linux_set.add_property_grid(cv, "Output");
  400. // Linux Application.
  401. cv = new PropertyGrid();
  402. cv.column_homogeneous = true;
  403. cv.add_row("Title", _linux_app_title);
  404. _linux_set.add_property_grid(cv, "Application");
  405. // Windows page.
  406. _windows_deploy_button = make_deploy_button(TargetPlatform.WINDOWS);
  407. _windows_deploy_button.clicked.connect(() => {
  408. // Validate input fields.
  409. string? output_path = _windows_output_path.get_filename();
  410. if (output_path == null) {
  411. loge("Select a valid output Destination");
  412. return;
  413. }
  414. string app_title = _windows_app_title.text.strip();
  415. if (app_title.length == 0) {
  416. loge("Enter a valid Title");
  417. return;
  418. }
  419. // Create the package.
  420. GLib.Variant paramz[] =
  421. {
  422. (string)output_path,
  423. int.parse(_windows_config.get_active_id()),
  424. app_title
  425. };
  426. GLib.Application.get_default().activate_action("create-package-windows"
  427. , new GLib.Variant.tuple(paramz));
  428. });
  429. _windows_output_path = new Gtk.FileChooserButton("Select folder", Gtk.FileChooserAction.SELECT_FOLDER);
  430. _windows_config = make_deploy_config_combo();
  431. _windows_app_title = new EntryText();
  432. _windows_app_title.placeholder_text = "My Application";
  433. _windows_app_title.text = _project.name();
  434. _windows_set = new PropertyGridSet();
  435. _windows_set.border_width = 12;
  436. // Windows box.
  437. _windows_box = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
  438. _windows_box.pack_start(_windows_deploy_button, false, true, 0);
  439. _windows_box.pack_start(_windows_set, false, true, 0);
  440. _windows_page = new DeployerPage(TargetPlatform.LINUX, _windows_box);
  441. // Windows General page.
  442. cv = new PropertyGrid();
  443. cv.column_homogeneous = true;
  444. cv.add_row("Destination", _windows_output_path);
  445. cv.add_row("Config", _windows_config);
  446. _windows_set.add_property_grid(cv, "Output");
  447. // Windows Application.
  448. cv = new PropertyGrid();
  449. cv.column_homogeneous = true;
  450. cv.add_row("Title", _windows_app_title);
  451. _windows_set.add_property_grid(cv, "Application");
  452. // Add pages.
  453. _notebook = new Gtk.Notebook();
  454. _notebook.append_page(_android_page, new Gtk.Label(TargetPlatform.ANDROID.to_label()));
  455. _notebook.append_page(_html5_page, new Gtk.Label(TargetPlatform.HTML5.to_label()));
  456. #if CROWN_PLATFORM_LINUX
  457. _notebook.append_page(_linux_page, new Gtk.Label(TargetPlatform.LINUX.to_label()));
  458. #elif CROWN_PLATFORM_WINDOWS
  459. _notebook.append_page(_windows_page, new Gtk.Label(TargetPlatform.WINDOWS.to_label()));
  460. #endif
  461. _notebook.vexpand = true;
  462. _notebook.show_border = false;
  463. this.get_content_area().border_width = 0;
  464. this.get_content_area().add(_notebook);
  465. }
  466. public void android_set_debug_keystore()
  467. {
  468. bool sensitive = !_android_use_debug_keystore.get_active();
  469. _android_keystore.sensitive = sensitive;
  470. _android_keystore_password.sensitive = sensitive;
  471. _android_key_alias.sensitive = sensitive;
  472. _android_key_password.sensitive = sensitive;
  473. }
  474. }
  475. } /* namespace Crown */