PrintJobInfoBlock.qml 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. // Copyright (c) 2018 Ultimaker B.V.
  2. // Cura is released under the terms of the LGPLv3 or higher.
  3. import QtQuick 2.2
  4. import QtQuick.Dialogs 1.1
  5. import QtQuick.Controls 2.0
  6. import QtQuick.Controls.Styles 1.4
  7. import QtGraphicalEffects 1.0
  8. import QtQuick.Layouts 1.1
  9. import QtQuick.Dialogs 1.1
  10. import UM 1.3 as UM
  11. Item {
  12. id: root;
  13. property var shadowRadius: UM.Theme.getSize("monitor_shadow_radius").width;
  14. property var shadowOffset: 2 * screenScaleFactor;
  15. property var debug: false;
  16. property var printJob: null;
  17. width: parent.width; // Bubbles downward
  18. height: childrenRect.height + shadowRadius * 2; // Bubbles upward
  19. UM.I18nCatalog {
  20. id: catalog;
  21. name: "cura";
  22. }
  23. // The actual card (white block)
  24. Rectangle {
  25. // 5px margin, but shifted 2px vertically because of the shadow
  26. anchors {
  27. bottomMargin: root.shadowRadius + root.shadowOffset;
  28. leftMargin: root.shadowRadius;
  29. rightMargin: root.shadowRadius;
  30. topMargin: root.shadowRadius - root.shadowOffset;
  31. }
  32. color: UM.Theme.getColor("monitor_card_background");
  33. height: childrenRect.height;
  34. layer.enabled: true
  35. layer.effect: DropShadow {
  36. radius: root.shadowRadius
  37. verticalOffset: 2 * screenScaleFactor
  38. color: "#3F000000" // 25% shadow
  39. }
  40. width: parent.width - shadowRadius * 2;
  41. Column {
  42. height: childrenRect.height;
  43. width: parent.width;
  44. // Main content
  45. Item {
  46. id: mainContent;
  47. height: 200 * screenScaleFactor; // TODO: Theme!
  48. width: parent.width;
  49. // Left content
  50. Item {
  51. anchors {
  52. bottom: parent.bottom;
  53. left: parent.left;
  54. margins: UM.Theme.getSize("wide_margin").width;
  55. right: parent.horizontalCenter;
  56. top: parent.top;
  57. }
  58. Item {
  59. id: printJobName;
  60. width: parent.width;
  61. height: UM.Theme.getSize("monitor_text_line").height;
  62. Rectangle {
  63. color: UM.Theme.getColor("monitor_skeleton_fill");
  64. height: parent.height;
  65. visible: !printJob;
  66. width: Math.round(parent.width / 3);
  67. }
  68. Label {
  69. anchors.fill: parent;
  70. color: UM.Theme.getColor("text");
  71. elide: Text.ElideRight;
  72. font: UM.Theme.getFont("default_bold");
  73. text: printJob && printJob.name ? printJob.name : ""; // Supress QML warnings
  74. visible: printJob;
  75. }
  76. }
  77. Item {
  78. id: printJobOwnerName;
  79. anchors {
  80. top: printJobName.bottom;
  81. topMargin: Math.floor(UM.Theme.getSize("default_margin").height / 2);
  82. }
  83. height: UM.Theme.getSize("monitor_text_line").height;
  84. width: parent.width;
  85. Rectangle {
  86. color: UM.Theme.getColor("monitor_skeleton_fill");
  87. height: parent.height;
  88. visible: !printJob;
  89. width: Math.round(parent.width / 2);
  90. }
  91. Label {
  92. anchors.fill: parent;
  93. color: UM.Theme.getColor("text");
  94. elide: Text.ElideRight;
  95. font: UM.Theme.getFont("default");
  96. text: printJob ? printJob.owner : ""; // Supress QML warnings
  97. visible: printJob;
  98. }
  99. }
  100. Item {
  101. id: printJobPreview;
  102. property var useUltibot: false;
  103. anchors {
  104. bottom: parent.bottom;
  105. horizontalCenter: parent.horizontalCenter;
  106. top: printJobOwnerName.bottom;
  107. topMargin: UM.Theme.getSize("default_margin").height;
  108. }
  109. width: height;
  110. // Skeleton
  111. Rectangle {
  112. anchors.fill: parent;
  113. color: UM.Theme.getColor("monitor_skeleton_fill");
  114. radius: UM.Theme.getSize("default_margin").width;
  115. visible: !printJob;
  116. }
  117. // Actual content
  118. Image {
  119. id: previewImage;
  120. anchors.fill: parent;
  121. opacity: printJob && printJob.state == "error" ? 0.5 : 1.0;
  122. source: printJob ? printJob.previewImageUrl : "";
  123. visible: printJob;
  124. }
  125. UM.RecolorImage {
  126. id: ultiBotImage;
  127. anchors.centerIn: printJobPreview;
  128. color: UM.Theme.getColor("monitor_placeholder_image");
  129. height: printJobPreview.height;
  130. source: "../svg/ultibot.svg";
  131. sourceSize {
  132. height: height;
  133. width: width;
  134. }
  135. /* Since print jobs ALWAYS have an image url, we have to check if that image URL errors or
  136. not in order to determine if we show the placeholder (ultibot) image instead. */
  137. visible: printJob && previewImage.status == Image.Error;
  138. width: printJobPreview.width;
  139. }
  140. UM.RecolorImage {
  141. id: statusImage;
  142. anchors.centerIn: printJobPreview;
  143. color: UM.Theme.getColor("monitor_image_overlay");
  144. height: 0.5 * printJobPreview.height;
  145. source: printJob && printJob.state == "error" ? "../svg/aborted-icon.svg" : "";
  146. sourceSize {
  147. height: height;
  148. width: width;
  149. }
  150. visible: source != "";
  151. width: 0.5 * printJobPreview.width;
  152. }
  153. }
  154. Label {
  155. id: totalTimeLabel;
  156. anchors {
  157. bottom: parent.bottom;
  158. right: parent.right;
  159. }
  160. color: UM.Theme.getColor("text");
  161. elide: Text.ElideRight;
  162. font: UM.Theme.getFont("default");
  163. text: printJob ? OutputDevice.formatDuration(printJob.timeTotal) : "";
  164. }
  165. }
  166. // Divider
  167. Rectangle {
  168. anchors {
  169. horizontalCenter: parent.horizontalCenter;
  170. verticalCenter: parent.verticalCenter;
  171. }
  172. color: !printJob ? UM.Theme.getColor("monitor_skeleton_fill") : UM.Theme.getColor("monitor_lining_light");
  173. height: parent.height - 2 * UM.Theme.getSize("default_margin").height;
  174. width: UM.Theme.getSize("default_lining").width;
  175. }
  176. // Right content
  177. Item {
  178. anchors {
  179. bottom: parent.bottom;
  180. left: parent.horizontalCenter;
  181. margins: UM.Theme.getSize("wide_margin").width;
  182. right: parent.right;
  183. top: parent.top;
  184. }
  185. Item {
  186. id: targetPrinterLabel;
  187. height: UM.Theme.getSize("monitor_text_line").height;
  188. width: parent.width;
  189. Rectangle {
  190. visible: !printJob;
  191. color: UM.Theme.getColor("monitor_skeleton_fill");
  192. anchors.fill: parent;
  193. }
  194. Label {
  195. color: UM.Theme.getColor("text");
  196. elide: Text.ElideRight;
  197. font: UM.Theme.getFont("default_bold");
  198. text: {
  199. if (printJob !== null) {
  200. if (printJob.assignedPrinter == null) {
  201. if (printJob.state == "error") {
  202. return catalog.i18nc("@label", "Waiting for: Unavailable printer");
  203. }
  204. return catalog.i18nc("@label", "Waiting for: First available");
  205. } else {
  206. return catalog.i18nc("@label", "Waiting for: ") + printJob.assignedPrinter.name;
  207. }
  208. }
  209. return "";
  210. }
  211. visible: printJob;
  212. }
  213. }
  214. PrinterInfoBlock {
  215. anchors.bottom: parent.bottom;
  216. printer: root.printJon && root.printJob.assignedPrinter;
  217. printJob: root.printJob;
  218. }
  219. }
  220. PrintJobContextMenu {
  221. id: contextButton;
  222. anchors {
  223. right: mainContent.right;
  224. rightMargin: UM.Theme.getSize("default_margin").width * 3 + root.shadowRadius;
  225. top: mainContent.top;
  226. topMargin: UM.Theme.getSize("default_margin").height;
  227. }
  228. printJob: root.printJob;
  229. visible: root.printJob;
  230. }
  231. }
  232. Item {
  233. id: configChangesBox;
  234. height: childrenRect.height;
  235. visible: printJob && printJob.configurationChanges.length !== 0;
  236. width: parent.width;
  237. // Config change toggle
  238. Rectangle {
  239. id: configChangeToggle;
  240. color: {
  241. if (configChangeToggleArea.containsMouse) {
  242. return UM.Theme.getColor("viewport_background"); // TODO: Theme!
  243. } else {
  244. return "transparent";
  245. }
  246. }
  247. width: parent.width;
  248. height: UM.Theme.getSize("default_margin").height * 4; // TODO: Theme!
  249. anchors {
  250. left: parent.left;
  251. right: parent.right;
  252. top: parent.top;
  253. }
  254. Rectangle {
  255. color: !printJob ? UM.Theme.getColor("monitor_skeleton_fill") : UM.Theme.getColor("monitor_lining_light");
  256. height: UM.Theme.getSize("default_lining").height;
  257. width: parent.width;
  258. }
  259. UM.RecolorImage {
  260. anchors {
  261. right: configChangeToggleLabel.left;
  262. rightMargin: UM.Theme.getSize("default_margin").width;
  263. verticalCenter: parent.verticalCenter;
  264. }
  265. color: UM.Theme.getColor("text");
  266. height: 23 * screenScaleFactor; // TODO: Theme!
  267. source: "../svg/warning-icon.svg";
  268. sourceSize {
  269. height: height;
  270. width: width;
  271. }
  272. width: 23 * screenScaleFactor; // TODO: Theme!
  273. }
  274. Label {
  275. id: configChangeToggleLabel;
  276. anchors {
  277. horizontalCenter: parent.horizontalCenter;
  278. verticalCenter: parent.verticalCenter;
  279. }
  280. color: UM.Theme.getColor("text");
  281. font: UM.Theme.getFont("default");
  282. text: catalog.i18nc("@label", "Configuration change");
  283. }
  284. UM.RecolorImage {
  285. anchors {
  286. left: configChangeToggleLabel.right;
  287. leftMargin: UM.Theme.getSize("default_margin").width;
  288. verticalCenter: parent.verticalCenter;
  289. }
  290. color: UM.Theme.getColor("text");
  291. height: 15 * screenScaleFactor; // TODO: Theme!
  292. source: {
  293. if (configChangeDetails.visible) {
  294. return UM.Theme.getIcon("arrow_top");
  295. } else {
  296. return UM.Theme.getIcon("arrow_bottom");
  297. }
  298. }
  299. sourceSize {
  300. width: width;
  301. height: height;
  302. }
  303. width: 15 * screenScaleFactor; // TODO: Theme!
  304. }
  305. MouseArea {
  306. id: configChangeToggleArea;
  307. anchors.fill: parent;
  308. hoverEnabled: true;
  309. onClicked: {
  310. configChangeDetails.visible = !configChangeDetails.visible;
  311. }
  312. }
  313. }
  314. // Config change details
  315. Item {
  316. id: configChangeDetails;
  317. anchors.top: configChangeToggle.bottom;
  318. Behavior on height { NumberAnimation { duration: 100 } }
  319. // In case of really massive multi-line configuration changes
  320. height: visible ? Math.max(UM.Theme.getSize("monitor_config_override_box").height, childrenRect.height) : 0;
  321. visible: false;
  322. width: parent.width;
  323. Item {
  324. anchors {
  325. bottomMargin: UM.Theme.getSize("wide_margin").height;
  326. fill: parent;
  327. leftMargin: UM.Theme.getSize("wide_margin").height * 4;
  328. rightMargin: UM.Theme.getSize("wide_margin").height * 4;
  329. topMargin: UM.Theme.getSize("wide_margin").height;
  330. }
  331. clip: true;
  332. Label {
  333. anchors.fill: parent;
  334. elide: Text.ElideRight;
  335. color: UM.Theme.getColor("text");
  336. font: UM.Theme.getFont("default");
  337. text: {
  338. if (!printJob || printJob.configurationChanges.length === 0) {
  339. return "";
  340. }
  341. var topLine;
  342. if (materialsAreKnown(printJob)) {
  343. topLine = catalog.i18nc("@label", "The assigned printer, %1, requires the following configuration change(s):").arg(printJob.assignedPrinter.name);
  344. } else {
  345. topLine = catalog.i18nc("@label", "The printer %1 is assigned, but the job contains an unknown material configuration.").arg(printJob.assignedPrinter.name);
  346. }
  347. var result = "<p>" + topLine +"</p>";
  348. for (var i = 0; i < printJob.configurationChanges.length; i++) {
  349. var change = printJob.configurationChanges[i];
  350. var text;
  351. switch (change.typeOfChange) {
  352. case "material_change":
  353. text = catalog.i18nc("@label", "Change material %1 from %2 to %3.").arg(change.index + 1).arg(change.originName).arg(change.targetName);
  354. break;
  355. case "material_insert":
  356. text = catalog.i18nc("@label", "Load %3 as material %1 (This cannot be overridden).").arg(change.index + 1).arg(change.targetName);
  357. break;
  358. case "print_core_change":
  359. text = catalog.i18nc("@label", "Change print core %1 from %2 to %3.").arg(change.index + 1).arg(change.originName).arg(change.targetName);
  360. break;
  361. case "buildplate_change":
  362. text = catalog.i18nc("@label", "Change build plate to %1 (This cannot be overridden).").arg(formatBuildPlateType(change.target_name));
  363. break;
  364. default:
  365. text = "";
  366. }
  367. result += "<p><b>" + text + "</b></p>";
  368. }
  369. return result;
  370. }
  371. wrapMode: Text.WordWrap;
  372. }
  373. Button {
  374. anchors {
  375. bottom: parent.bottom;
  376. left: parent.left;
  377. }
  378. background: Rectangle {
  379. border {
  380. color: UM.Theme.getColor("monitor_lining_heavy");
  381. width: UM.Theme.getSize("default_lining").width;
  382. }
  383. color: parent.hovered ? UM.Theme.getColor("monitor_card_background_inactive") : UM.Theme.getColor("monitor_card_background");
  384. implicitHeight: UM.Theme.getSize("default_margin").height * 3;
  385. implicitWidth: UM.Theme.getSize("default_margin").height * 8;
  386. }
  387. contentItem: Label {
  388. color: UM.Theme.getColor("text");
  389. font: UM.Theme.getFont("medium");
  390. horizontalAlignment: Text.AlignHCenter;
  391. text: parent.text;
  392. verticalAlignment: Text.AlignVCenter;
  393. }
  394. onClicked: {
  395. overrideConfirmationDialog.visible = true;
  396. }
  397. text: catalog.i18nc("@label", "Override");
  398. visible: {
  399. if (printJob && printJob.configurationChanges) {
  400. var length = printJob.configurationChanges.length;
  401. for (var i = 0; i < length; i++) {
  402. var typeOfChange = printJob.configurationChanges[i].typeOfChange;
  403. if (typeOfChange === "material_insert" || typeOfChange === "buildplate_change") {
  404. return false;
  405. }
  406. }
  407. }
  408. return true;
  409. }
  410. }
  411. }
  412. }
  413. MessageDialog {
  414. id: overrideConfirmationDialog;
  415. Component.onCompleted: visible = false;
  416. icon: StandardIcon.Warning;
  417. onYes: OutputDevice.forceSendJob(printJob.key);
  418. standardButtons: StandardButton.Yes | StandardButton.No;
  419. text: {
  420. if (!printJob) {
  421. return "";
  422. }
  423. var printJobName = formatPrintJobName(printJob.name);
  424. var confirmText = catalog.i18nc("@label", "Starting a print job with an incompatible configuration could damage your 3D printer. Are you sure you want to override the configuration and print %1?").arg(printJobName);
  425. return confirmText;
  426. }
  427. title: catalog.i18nc("@window:title", "Override configuration configuration and start print");
  428. }
  429. }
  430. }
  431. }
  432. // Utils
  433. function formatPrintJobName(name) {
  434. var extensions = [ ".gz", ".gcode", ".ufp" ];
  435. for (var i = 0; i < extensions.length; i++) {
  436. var extension = extensions[i];
  437. if (name.slice(-extension.length) === extension) {
  438. name = name.substring(0, name.length - extension.length);
  439. }
  440. }
  441. return name;
  442. }
  443. function materialsAreKnown(job) {
  444. var conf0 = job.configuration[0];
  445. if (conf0 && !conf0.material.material) {
  446. return false;
  447. }
  448. var conf1 = job.configuration[1];
  449. if (conf1 && !conf1.material.material) {
  450. return false;
  451. }
  452. return true;
  453. }
  454. function formatBuildPlateType(buildPlateType) {
  455. var translationText = "";
  456. switch (buildPlateType) {
  457. case "glass":
  458. translationText = catalog.i18nc("@label", "Glass");
  459. break;
  460. case "aluminum":
  461. translationText = catalog.i18nc("@label", "Aluminum");
  462. break;
  463. default:
  464. translationText = null;
  465. }
  466. return translationText;
  467. }
  468. }