ProfilesPage.qml 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  1. // Copyright (c) 2019 Ultimaker B.V.
  2. // Uranium is released under the terms of the LGPLv3 or higher.
  3. import QtQuick 2.7
  4. import QtQuick.Controls 1.4
  5. import QtQuick.Layouts 1.3
  6. import QtQuick.Dialogs 1.2
  7. import UM 1.2 as UM
  8. import Cura 1.6 as Cura
  9. Item
  10. {
  11. id: base
  12. property var resetEnabled: false // Keep PreferencesDialog happy
  13. property var extrudersModel: CuraApplication.getExtrudersModel()
  14. property var qualityManagementModel: CuraApplication.getQualityManagementModel()
  15. UM.I18nCatalog { id: catalog; name: "cura"; }
  16. Label
  17. {
  18. id: titleLabel
  19. anchors
  20. {
  21. top: parent.top
  22. left: parent.left
  23. right: parent.right
  24. margins: 5 * screenScaleFactor
  25. }
  26. font.pointSize: 18
  27. text: catalog.i18nc("@title:tab", "Profiles")
  28. }
  29. property var hasCurrentItem: base.currentItem != null
  30. property var currentItem:
  31. {
  32. var current_index = qualityListView.currentIndex;
  33. return (current_index == -1) ? null : base.qualityManagementModel.getItem(current_index);
  34. }
  35. property var currentItemName: hasCurrentItem ? base.currentItem.name : ""
  36. property var currentItemDisplayName: hasCurrentItem ? base.qualityManagementModel.getQualityItemDisplayName(base.currentItem) : ""
  37. property var isCurrentItemActivated:
  38. {
  39. if (!base.currentItem)
  40. {
  41. return false;
  42. }
  43. if (base.currentItem.is_read_only)
  44. {
  45. return (base.currentItem.name == Cura.MachineManager.activeQualityOrQualityChangesName) && (base.currentItem.intent_category == Cura.MachineManager.activeIntentCategory);
  46. }
  47. else
  48. {
  49. return base.currentItem.name == Cura.MachineManager.activeQualityOrQualityChangesName;
  50. }
  51. }
  52. property var canCreateProfile:
  53. {
  54. return isCurrentItemActivated && Cura.MachineManager.hasUserSettings;
  55. }
  56. Row // Button Row
  57. {
  58. id: buttonRow
  59. anchors
  60. {
  61. left: parent.left
  62. right: parent.right
  63. top: titleLabel.bottom
  64. }
  65. height: childrenRect.height
  66. // Activate button
  67. Button
  68. {
  69. id: activateMenuButton
  70. text: catalog.i18nc("@action:button", "Activate")
  71. iconName: "list-activate"
  72. enabled: !isCurrentItemActivated
  73. onClicked:
  74. {
  75. if (base.currentItem.is_read_only)
  76. {
  77. Cura.IntentManager.selectIntent(base.currentItem.intent_category, base.currentItem.quality_type);
  78. }
  79. else
  80. {
  81. Cura.MachineManager.setQualityChangesGroup(base.currentItem.quality_changes_group);
  82. }
  83. }
  84. }
  85. // Create button
  86. Button
  87. {
  88. id: createMenuButton
  89. text: catalog.i18nc("@label", "Create")
  90. iconName: "list-add"
  91. enabled: base.canCreateProfile && !Cura.MachineManager.stacksHaveErrors
  92. visible: base.canCreateProfile
  93. onClicked:
  94. {
  95. createQualityDialog.object = Cura.ContainerManager.makeUniqueName(base.currentItem.name);
  96. createQualityDialog.open();
  97. createQualityDialog.selectText();
  98. }
  99. }
  100. // Duplicate button
  101. Button
  102. {
  103. id: duplicateMenuButton
  104. text: catalog.i18nc("@label", "Duplicate")
  105. iconName: "list-add"
  106. enabled: !base.canCreateProfile
  107. visible: !base.canCreateProfile
  108. onClicked:
  109. {
  110. duplicateQualityDialog.object = Cura.ContainerManager.makeUniqueName(base.currentItem.name);
  111. duplicateQualityDialog.open();
  112. duplicateQualityDialog.selectText();
  113. }
  114. }
  115. // Remove button
  116. Button
  117. {
  118. id: removeMenuButton
  119. text: catalog.i18nc("@action:button", "Remove")
  120. iconName: "list-remove"
  121. enabled: base.hasCurrentItem && !base.currentItem.is_read_only && !base.isCurrentItemActivated
  122. onClicked:
  123. {
  124. forceActiveFocus();
  125. confirmRemoveQualityDialog.open();
  126. }
  127. }
  128. // Rename button
  129. Button
  130. {
  131. id: renameMenuButton
  132. text: catalog.i18nc("@action:button", "Rename")
  133. iconName: "edit-rename"
  134. enabled: base.hasCurrentItem && !base.currentItem.is_read_only
  135. onClicked:
  136. {
  137. renameQualityDialog.object = base.currentItem.name;
  138. renameQualityDialog.open();
  139. renameQualityDialog.selectText();
  140. }
  141. }
  142. // Import button
  143. Button
  144. {
  145. id: importMenuButton
  146. text: catalog.i18nc("@action:button", "Import")
  147. iconName: "document-import"
  148. onClicked:
  149. {
  150. importDialog.open();
  151. }
  152. }
  153. // Export button
  154. Button
  155. {
  156. id: exportMenuButton
  157. text: catalog.i18nc("@action:button", "Export")
  158. iconName: "document-export"
  159. enabled: base.hasCurrentItem && !base.currentItem.is_read_only
  160. onClicked:
  161. {
  162. exportDialog.open();
  163. }
  164. }
  165. }
  166. // Click create profile from ... in Profile context menu
  167. signal createProfile()
  168. onCreateProfile:
  169. {
  170. createQualityDialog.object = Cura.ContainerManager.makeUniqueName(Cura.MachineManager.activeQualityOrQualityChangesName);
  171. createQualityDialog.open();
  172. createQualityDialog.selectText();
  173. }
  174. // Dialog to request a name when creating a new profile
  175. UM.RenameDialog
  176. {
  177. id: createQualityDialog
  178. title: catalog.i18nc("@title:window", "Create Profile")
  179. object: "<new name>"
  180. explanation: catalog.i18nc("@info", "Please provide a name for this profile.")
  181. onAccepted:
  182. {
  183. base.newQualityNameToSelect = newName; // We want to switch to the new profile once it's created
  184. base.toActivateNewQuality = true;
  185. base.qualityManagementModel.createQualityChanges(newName);
  186. }
  187. }
  188. property string newQualityNameToSelect: ""
  189. property bool toActivateNewQuality: false
  190. // This connection makes sure that we will switch to the correct quality after the model gets updated
  191. Connections
  192. {
  193. target: base.qualityManagementModel
  194. onItemsChanged:
  195. {
  196. var toSelectItemName = base.currentItem == null ? "" : base.currentItem.name;
  197. if (newQualityNameToSelect != "")
  198. {
  199. toSelectItemName = newQualityNameToSelect;
  200. }
  201. var newIdx = -1; // Default to nothing if nothing can be found
  202. if (toSelectItemName != "")
  203. {
  204. // Select the required quality name if given
  205. for (var idx = 0; idx < base.qualityManagementModel.count; ++idx)
  206. {
  207. var item = base.qualityManagementModel.getItem(idx);
  208. if (item.name == toSelectItemName)
  209. {
  210. // Switch to the newly created profile if needed
  211. newIdx = idx;
  212. if (base.toActivateNewQuality)
  213. {
  214. // Activate this custom quality if required
  215. Cura.MachineManager.setQualityChangesGroup(item.quality_changes_group);
  216. }
  217. break;
  218. }
  219. }
  220. }
  221. qualityListView.currentIndex = newIdx;
  222. // Reset states
  223. base.newQualityNameToSelect = "";
  224. base.toActivateNewQuality = false;
  225. }
  226. }
  227. // Dialog to request a name when duplicating a new profile
  228. UM.RenameDialog
  229. {
  230. id: duplicateQualityDialog
  231. title: catalog.i18nc("@title:window", "Duplicate Profile")
  232. object: "<new name>"
  233. onAccepted:
  234. {
  235. base.qualityManagementModel.duplicateQualityChanges(newName, base.currentItem);
  236. }
  237. }
  238. // Confirmation dialog for removing a profile
  239. MessageDialog
  240. {
  241. id: confirmRemoveQualityDialog
  242. icon: StandardIcon.Question;
  243. title: catalog.i18nc("@title:window", "Confirm Remove")
  244. text: catalog.i18nc("@label (%1 is object name)", "Are you sure you wish to remove %1? This cannot be undone!").arg(base.currentItemName)
  245. standardButtons: StandardButton.Yes | StandardButton.No
  246. modality: Qt.ApplicationModal
  247. onYes:
  248. {
  249. base.qualityManagementModel.removeQualityChangesGroup(base.currentItem.quality_changes_group);
  250. // reset current item to the first if available
  251. qualityListView.currentIndex = -1; // Reset selection.
  252. }
  253. }
  254. // Dialog to rename a quality profile
  255. UM.RenameDialog
  256. {
  257. id: renameQualityDialog
  258. title: catalog.i18nc("@title:window", "Rename Profile")
  259. object: "<new name>"
  260. onAccepted:
  261. {
  262. var actualNewName = base.qualityManagementModel.renameQualityChangesGroup(base.currentItem.quality_changes_group, newName);
  263. base.newQualityNameToSelect = actualNewName; // Select the new name after the model gets updated
  264. }
  265. }
  266. // Dialog for importing a quality profile
  267. FileDialog
  268. {
  269. id: importDialog
  270. title: catalog.i18nc("@title:window", "Import Profile")
  271. selectExisting: true
  272. nameFilters: base.qualityManagementModel.getFileNameFilters("profile_reader")
  273. folder: CuraApplication.getDefaultPath("dialog_profile_path")
  274. onAccepted:
  275. {
  276. var result = Cura.ContainerManager.importProfile(fileUrl);
  277. messageDialog.text = result.message;
  278. if (result.status == "ok")
  279. {
  280. messageDialog.icon = StandardIcon.Information;
  281. }
  282. else if (result.status == "warning" || result.status == "duplicate")
  283. {
  284. messageDialog.icon = StandardIcon.Warning;
  285. }
  286. else
  287. {
  288. messageDialog.icon = StandardIcon.Critical;
  289. }
  290. messageDialog.open();
  291. CuraApplication.setDefaultPath("dialog_profile_path", folder);
  292. }
  293. }
  294. // Dialog for exporting a quality profile
  295. FileDialog
  296. {
  297. id: exportDialog
  298. title: catalog.i18nc("@title:window", "Export Profile")
  299. selectExisting: false
  300. nameFilters: base.qualityManagementModel.getFileNameFilters("profile_writer")
  301. folder: CuraApplication.getDefaultPath("dialog_profile_path")
  302. onAccepted:
  303. {
  304. var result = Cura.ContainerManager.exportQualityChangesGroup(base.currentItem.quality_changes_group,
  305. fileUrl, selectedNameFilter);
  306. if (result && result.status == "error")
  307. {
  308. messageDialog.icon = StandardIcon.Critical;
  309. messageDialog.text = result.message;
  310. messageDialog.open();
  311. }
  312. // else pop-up Message thing from python code
  313. CuraApplication.setDefaultPath("dialog_profile_path", folder);
  314. }
  315. }
  316. Item
  317. {
  318. id: contentsItem
  319. anchors
  320. {
  321. top: titleLabel.bottom
  322. left: parent.left
  323. right: parent.right
  324. bottom: parent.bottom
  325. margins: 5 * screenScaleFactor
  326. bottomMargin: 0
  327. }
  328. clip: true
  329. }
  330. Item
  331. {
  332. anchors
  333. {
  334. top: buttonRow.bottom
  335. topMargin: UM.Theme.getSize("default_margin").height
  336. left: parent.left
  337. right: parent.right
  338. bottom: parent.bottom
  339. }
  340. SystemPalette
  341. {
  342. id: palette
  343. }
  344. Label
  345. {
  346. id: captionLabel
  347. anchors
  348. {
  349. top: parent.top
  350. left: parent.left
  351. }
  352. visible: text != ""
  353. text: catalog.i18nc("@label %1 is printer name", "Printer: %1").arg(Cura.MachineManager.activeMachine.name)
  354. width: profileScrollView.width
  355. elide: Text.ElideRight
  356. }
  357. ScrollView
  358. {
  359. id: profileScrollView
  360. anchors
  361. {
  362. top: captionLabel.visible ? captionLabel.bottom : parent.top
  363. topMargin: captionLabel.visible ? UM.Theme.getSize("default_margin").height : 0
  364. bottom: parent.bottom
  365. left: parent.left
  366. }
  367. Rectangle
  368. {
  369. parent: viewport
  370. anchors.fill: parent
  371. color: palette.light
  372. }
  373. width: true ? (parent.width * 0.4) | 0 : parent.width
  374. frameVisible: true
  375. clip: true
  376. ListView
  377. {
  378. id: qualityListView
  379. model: base.qualityManagementModel
  380. Component.onCompleted:
  381. {
  382. var selectedItemName = Cura.MachineManager.activeQualityOrQualityChangesName;
  383. // Select the required quality name if given
  384. for (var idx = 0; idx < base.qualityManagementModel.count; idx++)
  385. {
  386. var item = base.qualityManagementModel.getItem(idx);
  387. if (item.name == selectedItemName)
  388. {
  389. currentIndex = idx;
  390. break;
  391. }
  392. }
  393. }
  394. section.property: "section_name"
  395. section.delegate: Rectangle
  396. {
  397. height: childrenRect.height
  398. Label
  399. {
  400. anchors.left: parent.left
  401. anchors.leftMargin: UM.Theme.getSize("default_lining").width
  402. text: section
  403. font.bold: true
  404. }
  405. }
  406. delegate: Rectangle
  407. {
  408. width: profileScrollView.width
  409. height: childrenRect.height
  410. // Added this property to identify custom profiles in automated system tests (Squish)
  411. property bool isReadOnly: model.is_read_only
  412. property bool isCurrentItem: ListView.isCurrentItem
  413. color: isCurrentItem ? palette.highlight : (model.index % 2) ? palette.base : palette.alternateBase
  414. Label
  415. {
  416. anchors.left: parent.left
  417. anchors.leftMargin: UM.Theme.getSize("default_margin").width
  418. anchors.right: parent.right
  419. width: Math.floor((parent.width * 0.8))
  420. text: model.name
  421. elide: Text.ElideRight
  422. font.italic:
  423. {
  424. if (model.is_read_only)
  425. {
  426. // For built-in qualities, it needs to match both the intent category and the quality name
  427. return model.name == Cura.MachineManager.activeQualityOrQualityChangesName && model.intent_category == Cura.MachineManager.activeIntentCategory
  428. }
  429. else
  430. {
  431. // For custom qualities, it only needs to match the name
  432. return model.name == Cura.MachineManager.activeQualityOrQualityChangesName
  433. }
  434. }
  435. color: parent.isCurrentItem ? palette.highlightedText : palette.text
  436. }
  437. MouseArea
  438. {
  439. anchors.fill: parent
  440. onClicked:
  441. {
  442. parent.ListView.view.currentIndex = model.index;
  443. }
  444. }
  445. }
  446. }
  447. }
  448. // details panel on the right
  449. Item
  450. {
  451. id: detailsPanel
  452. anchors
  453. {
  454. left: profileScrollView.right
  455. leftMargin: UM.Theme.getSize("default_margin").width
  456. top: parent.top
  457. bottom: parent.bottom
  458. right: parent.right
  459. }
  460. Item
  461. {
  462. anchors.fill: parent
  463. visible: base.currentItem != null
  464. Item // Profile title Label
  465. {
  466. id: profileName
  467. width: parent.width
  468. height: childrenRect.height
  469. Label
  470. {
  471. anchors.left: parent.left
  472. anchors.right: parent.right
  473. text: base.currentItemDisplayName
  474. font: UM.Theme.getFont("large_bold")
  475. elide: Text.ElideRight
  476. renderType: Text.NativeRendering
  477. }
  478. }
  479. Flow
  480. {
  481. id: currentSettingsActions
  482. visible: base.hasCurrentItem && base.currentItem.name == Cura.MachineManager.activeQualityOrQualityChangesName && base.currentItem.intent_category == Cura.MachineManager.activeIntentCategory
  483. anchors.left: parent.left
  484. anchors.right: parent.right
  485. anchors.top: profileName.bottom
  486. anchors.topMargin: UM.Theme.getSize("default_margin").height
  487. Button
  488. {
  489. text: catalog.i18nc("@action:button", "Update profile with current settings/overrides")
  490. enabled: Cura.MachineManager.hasUserSettings && !base.currentItem.is_read_only
  491. onClicked: Cura.ContainerManager.updateQualityChanges()
  492. }
  493. Button
  494. {
  495. text: catalog.i18nc("@action:button", "Discard current changes");
  496. enabled: Cura.MachineManager.hasUserSettings
  497. onClicked: Cura.ContainerManager.clearUserContainers();
  498. }
  499. }
  500. Column
  501. {
  502. id: profileNotices
  503. anchors.top: currentSettingsActions.visible ? currentSettingsActions.bottom : currentSettingsActions.anchors.top
  504. anchors.topMargin: UM.Theme.getSize("default_margin").height
  505. anchors.left: parent.left
  506. anchors.right: parent.right
  507. spacing: UM.Theme.getSize("default_margin").height
  508. Label
  509. {
  510. id: defaultsMessage
  511. visible: false
  512. text: catalog.i18nc("@action:label", "This profile uses the defaults specified by the printer, so it has no settings/overrides in the list below.")
  513. wrapMode: Text.WordWrap
  514. width: parent.width
  515. }
  516. Label
  517. {
  518. id: noCurrentSettingsMessage
  519. visible: base.isCurrentItemActivated && !Cura.MachineManager.hasUserSettings
  520. text: catalog.i18nc("@action:label", "Your current settings match the selected profile.")
  521. wrapMode: Text.WordWrap
  522. width: parent.width
  523. }
  524. }
  525. TabView
  526. {
  527. anchors.left: parent.left
  528. anchors.top: profileNotices.visible ? profileNotices.bottom : profileNotices.anchors.top
  529. anchors.topMargin: UM.Theme.getSize("default_margin").height
  530. anchors.right: parent.right
  531. anchors.bottom: parent.bottom
  532. currentIndex: 0
  533. ProfileTab
  534. {
  535. title: catalog.i18nc("@title:tab", "Global Settings")
  536. qualityItem: base.currentItem
  537. }
  538. Repeater
  539. {
  540. model: base.extrudersModel
  541. ProfileTab
  542. {
  543. title: model.name
  544. extruderPosition: model.index
  545. qualityItem: base.currentItem
  546. }
  547. }
  548. }
  549. }
  550. }
  551. }
  552. }