ProfilesPage.qml 19 KB

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