Cura.qml 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757
  1. // Copyright (c) 2018 Ultimaker B.V.
  2. // Cura is released under the terms of the LGPLv3 or higher.
  3. import QtQuick 2.7
  4. import QtQuick.Controls 1.4
  5. import QtQuick.Controls.Styles 1.4
  6. import QtQuick.Layouts 1.1
  7. import QtQuick.Dialogs 1.2
  8. import UM 1.3 as UM
  9. import Cura 1.1 as Cura
  10. import "Dialogs"
  11. import "Menus"
  12. import "Skeleton"
  13. UM.MainWindow
  14. {
  15. id: base
  16. // Cura application window title
  17. title: catalog.i18nc("@title:window", "Ultimaker Cura")
  18. viewportRect: Qt.rect(0, 0, 1.0, 1.0)
  19. backgroundColor: UM.Theme.getColor("viewport_background")
  20. UM.I18nCatalog
  21. {
  22. id: catalog
  23. name:"cura"
  24. }
  25. function showTooltip(item, position, text)
  26. {
  27. tooltip.text = text;
  28. position = item.mapToItem(backgroundItem, position.x - UM.Theme.getSize("default_arrow").width, position.y);
  29. tooltip.show(position);
  30. }
  31. function hideTooltip()
  32. {
  33. tooltip.hide();
  34. }
  35. Component.onCompleted:
  36. {
  37. CuraApplication.setMinimumWindowSize(UM.Theme.getSize("window_minimum_size"))
  38. // Workaround silly issues with QML Action's shortcut property.
  39. //
  40. // Currently, there is no way to define shortcuts as "Application Shortcut".
  41. // This means that all Actions are "Window Shortcuts". The code for this
  42. // implements a rather naive check that just checks if any of the action's parents
  43. // are a window. Since the "Actions" object is a singleton it has no parent by
  44. // default. If we set its parent to something contained in this window, the
  45. // shortcut will activate properly because one of its parents is a window.
  46. //
  47. // This has been fixed for QtQuick Controls 2 since the Shortcut item has a context property.
  48. Cura.Actions.parent = backgroundItem
  49. CuraApplication.purgeWindows()
  50. }
  51. Item
  52. {
  53. id: backgroundItem
  54. anchors.fill: parent
  55. SidebarTooltip
  56. {
  57. id: tooltip
  58. }
  59. signal hasMesh(string name) //this signal sends the filebase name so it can be used for the JobSpecs.qml
  60. function getMeshName(path)
  61. {
  62. //takes the path the complete path of the meshname and returns only the filebase
  63. var fileName = path.slice(path.lastIndexOf("/") + 1)
  64. var fileBase = fileName.slice(0, fileName.indexOf("."))
  65. return fileBase
  66. }
  67. //DeleteSelection on the keypress backspace event
  68. Keys.onPressed:
  69. {
  70. if (event.key == Qt.Key_Backspace)
  71. {
  72. Cura.Actions.deleteSelection.trigger()
  73. }
  74. }
  75. ApplicationMenu
  76. {
  77. id: applicationMenu
  78. window: base
  79. }
  80. TopHeader
  81. {
  82. id: topHeader
  83. anchors
  84. {
  85. left: parent.left
  86. right: parent.right
  87. top: applicationMenu.bottom
  88. }
  89. }
  90. Item
  91. {
  92. id: contentItem
  93. anchors
  94. {
  95. top: topHeader.bottom
  96. bottom: parent.bottom
  97. left: parent.left
  98. right: parent.right
  99. }
  100. Keys.forwardTo: applicationMenu
  101. DropArea
  102. {
  103. anchors.fill: parent
  104. onDropped:
  105. {
  106. if (drop.urls.length > 0)
  107. {
  108. var nonPackages = [];
  109. for (var i = 0; i < drop.urls.length; i++)
  110. {
  111. var filename = drop.urls[i];
  112. if (filename.endsWith(".curapackage"))
  113. {
  114. // Try to install plugin & close.
  115. CuraApplication.getPackageManager().installPackageViaDragAndDrop(filename);
  116. packageInstallDialog.text = catalog.i18nc("@label", "This package will be installed after restarting.");
  117. packageInstallDialog.icon = StandardIcon.Information;
  118. packageInstallDialog.open();
  119. }
  120. else
  121. {
  122. nonPackages.push(filename);
  123. }
  124. }
  125. openDialog.handleOpenFileUrls(nonPackages);
  126. }
  127. }
  128. }
  129. Loader
  130. {
  131. // The stage menu is, as the name implies, a menu that is defined by the active stage.
  132. // Note that this menu does not need to be set at all! It's perfectly acceptable to have a stage
  133. // without this menu!
  134. id: stageMenu
  135. anchors
  136. {
  137. left: parent.left
  138. right: parent.right
  139. top: parent.top
  140. margins: UM.Theme.getSize("default_margin").height
  141. }
  142. height: 50
  143. source: UM.Controller.activeStage.stageMenuComponent
  144. }
  145. JobSpecs
  146. {
  147. id: jobSpecs
  148. anchors
  149. {
  150. bottom: parent.bottom
  151. bottomMargin: UM.Theme.getSize("default_margin").height
  152. rightMargin: UM.Theme.getSize("default_margin").width
  153. }
  154. }
  155. Toolbar
  156. {
  157. id: toolbar
  158. property int mouseX: base.mouseX
  159. property int mouseY: base.mouseY
  160. anchors {
  161. top: stageMenu.bottom
  162. topMargin: UM.Theme.getSize("window_margin").height
  163. left: parent.left
  164. }
  165. }
  166. ObjectsList
  167. {
  168. id: objectsList
  169. visible: UM.Preferences.getValue("cura/use_multi_build_plate")
  170. anchors
  171. {
  172. bottom: parent.bottom
  173. left: parent.left
  174. }
  175. }
  176. Loader
  177. {
  178. id: main
  179. anchors
  180. {
  181. top: parent.top
  182. bottom: parent.bottom
  183. left: parent.left
  184. right: parent.right
  185. }
  186. MouseArea
  187. {
  188. visible: UM.Controller.activeStage.mainComponent != ""
  189. anchors.fill: parent
  190. acceptedButtons: Qt.AllButtons
  191. onWheel: wheel.accepted = true
  192. }
  193. source: UM.Controller.activeStage.mainComponent
  194. }
  195. UM.MessageStack
  196. {
  197. anchors
  198. {
  199. horizontalCenter: parent.horizontalCenter
  200. horizontalCenterOffset: -(Math.round(UM.Theme.getSize("sidebar").width / 2))
  201. top: parent.verticalCenter
  202. bottom: parent.bottom
  203. bottomMargin: UM.Theme.getSize("default_margin").height
  204. }
  205. }
  206. ViewOrientationControls
  207. {
  208. id: viewOrientationControls
  209. anchors
  210. {
  211. left: parent.left
  212. margins: UM.Theme.getSize("default_margin").width
  213. bottom: parent.bottom
  214. }
  215. }
  216. ProgressAndSaveWidget
  217. {
  218. anchors.right: parent.right
  219. anchors.bottom: parent.bottom
  220. width: UM.Theme.getSize("sidebar").width
  221. anchors.rightMargin: UM.Theme.getSize("sidebar_margin").width
  222. anchors.bottomMargin: UM.Theme.getSize("sidebar_margin").height
  223. onShowTooltip:
  224. {
  225. base.showTooltip(item, location, text)
  226. }
  227. onHideTooltip:
  228. {
  229. base.hideTooltip()
  230. }
  231. }
  232. }
  233. }
  234. UM.PreferencesDialog
  235. {
  236. id: preferences
  237. Component.onCompleted:
  238. {
  239. //; Remove & re-add the general page as we want to use our own instead of uranium standard.
  240. removePage(0);
  241. insertPage(0, catalog.i18nc("@title:tab","General"), Qt.resolvedUrl("Preferences/GeneralPage.qml"));
  242. removePage(1);
  243. insertPage(1, catalog.i18nc("@title:tab","Settings"), Qt.resolvedUrl("Preferences/SettingVisibilityPage.qml"));
  244. insertPage(2, catalog.i18nc("@title:tab", "Printers"), Qt.resolvedUrl("Preferences/MachinesPage.qml"));
  245. insertPage(3, catalog.i18nc("@title:tab", "Materials"), Qt.resolvedUrl("Preferences/Materials/MaterialsPage.qml"));
  246. insertPage(4, catalog.i18nc("@title:tab", "Profiles"), Qt.resolvedUrl("Preferences/ProfilesPage.qml"));
  247. // Remove plug-ins page because we will use the shiny new plugin browser:
  248. removePage(5);
  249. //Force refresh
  250. setPage(0);
  251. }
  252. onVisibleChanged:
  253. {
  254. // When the dialog closes, switch to the General page.
  255. // This prevents us from having a heavy page like Setting Visiblity active in the background.
  256. setPage(0);
  257. }
  258. }
  259. Connections
  260. {
  261. target: Cura.Actions.preferences
  262. onTriggered: preferences.visible = true
  263. }
  264. Connections
  265. {
  266. target: CuraApplication
  267. onShowPreferencesWindow: preferences.visible = true
  268. }
  269. Connections
  270. {
  271. target: Cura.Actions.addProfile
  272. onTriggered:
  273. {
  274. preferences.show();
  275. preferences.setPage(4);
  276. // Create a new profile after a very short delay so the preference page has time to initiate
  277. createProfileTimer.start();
  278. }
  279. }
  280. Connections
  281. {
  282. target: Cura.Actions.configureMachines
  283. onTriggered:
  284. {
  285. preferences.visible = true;
  286. preferences.setPage(2);
  287. }
  288. }
  289. Connections
  290. {
  291. target: Cura.Actions.manageProfiles
  292. onTriggered:
  293. {
  294. preferences.visible = true;
  295. preferences.setPage(4);
  296. }
  297. }
  298. Connections
  299. {
  300. target: Cura.Actions.manageMaterials
  301. onTriggered:
  302. {
  303. preferences.visible = true;
  304. preferences.setPage(3)
  305. }
  306. }
  307. Connections
  308. {
  309. target: Cura.Actions.configureSettingVisibility
  310. onTriggered:
  311. {
  312. preferences.visible = true;
  313. preferences.setPage(1);
  314. if(source && source.key)
  315. {
  316. preferences.getCurrentItem().scrollToSection(source.key);
  317. }
  318. }
  319. }
  320. Timer
  321. {
  322. id: createProfileTimer
  323. repeat: false
  324. interval: 1
  325. onTriggered: preferences.getCurrentItem().createProfile()
  326. }
  327. // BlurSettings is a way to force the focus away from any of the setting items.
  328. // We need to do this in order to keep the bindings intact.
  329. Connections
  330. {
  331. target: Cura.MachineManager
  332. onBlurSettings:
  333. {
  334. contentItem.forceActiveFocus()
  335. }
  336. }
  337. ContextMenu
  338. {
  339. id: contextMenu
  340. }
  341. onPreClosing:
  342. {
  343. close.accepted = CuraApplication.getIsAllChecksPassed();
  344. if (!close.accepted)
  345. {
  346. CuraApplication.checkAndExitApplication();
  347. }
  348. }
  349. MessageDialog
  350. {
  351. id: exitConfirmationDialog
  352. title: catalog.i18nc("@title:window", "Closing Cura")
  353. text: catalog.i18nc("@label", "Are you sure you want to exit Cura?")
  354. icon: StandardIcon.Question
  355. modality: Qt.ApplicationModal
  356. standardButtons: StandardButton.Yes | StandardButton.No
  357. onYes: CuraApplication.callConfirmExitDialogCallback(true)
  358. onNo: CuraApplication.callConfirmExitDialogCallback(false)
  359. onRejected: CuraApplication.callConfirmExitDialogCallback(false)
  360. onVisibilityChanged:
  361. {
  362. if (!visible)
  363. {
  364. // reset the text to default because other modules may change the message text.
  365. text = catalog.i18nc("@label", "Are you sure you want to exit Cura?");
  366. }
  367. }
  368. }
  369. Connections
  370. {
  371. target: CuraApplication
  372. onShowConfirmExitDialog:
  373. {
  374. exitConfirmationDialog.text = message;
  375. exitConfirmationDialog.open();
  376. }
  377. }
  378. Connections
  379. {
  380. target: Cura.Actions.quit
  381. onTriggered: CuraApplication.checkAndExitApplication();
  382. }
  383. Connections
  384. {
  385. target: Cura.Actions.toggleFullScreen
  386. onTriggered: base.toggleFullscreen();
  387. }
  388. FileDialog
  389. {
  390. id: openDialog;
  391. //: File open dialog title
  392. title: catalog.i18nc("@title:window","Open file(s)")
  393. modality: UM.Application.platform == "linux" ? Qt.NonModal : Qt.WindowModal;
  394. selectMultiple: true
  395. nameFilters: UM.MeshFileHandler.supportedReadFileTypes;
  396. folder: CuraApplication.getDefaultPath("dialog_load_path")
  397. onAccepted:
  398. {
  399. // Because several implementations of the file dialog only update the folder
  400. // when it is explicitly set.
  401. var f = folder;
  402. folder = f;
  403. CuraApplication.setDefaultPath("dialog_load_path", folder);
  404. handleOpenFileUrls(fileUrls);
  405. }
  406. // Yeah... I know... it is a mess to put all those things here.
  407. // There are lots of user interactions in this part of the logic, such as showing a warning dialog here and there,
  408. // etc. This means it will come back and forth from time to time between QML and Python. So, separating the logic
  409. // and view here may require more effort but make things more difficult to understand.
  410. function handleOpenFileUrls(fileUrlList)
  411. {
  412. // look for valid project files
  413. var projectFileUrlList = [];
  414. var hasGcode = false;
  415. var nonGcodeFileList = [];
  416. for (var i in fileUrlList)
  417. {
  418. var endsWithG = /\.g$/;
  419. var endsWithGcode = /\.gcode$/;
  420. if (endsWithG.test(fileUrlList[i]) || endsWithGcode.test(fileUrlList[i]))
  421. {
  422. continue;
  423. }
  424. else if (CuraApplication.checkIsValidProjectFile(fileUrlList[i]))
  425. {
  426. projectFileUrlList.push(fileUrlList[i]);
  427. }
  428. nonGcodeFileList.push(fileUrlList[i]);
  429. }
  430. hasGcode = nonGcodeFileList.length < fileUrlList.length;
  431. // show a warning if selected multiple files together with Gcode
  432. var hasProjectFile = projectFileUrlList.length > 0;
  433. var selectedMultipleFiles = fileUrlList.length > 1;
  434. if (selectedMultipleFiles && hasGcode)
  435. {
  436. infoMultipleFilesWithGcodeDialog.selectedMultipleFiles = selectedMultipleFiles;
  437. infoMultipleFilesWithGcodeDialog.hasProjectFile = hasProjectFile;
  438. infoMultipleFilesWithGcodeDialog.fileUrls = nonGcodeFileList.slice();
  439. infoMultipleFilesWithGcodeDialog.projectFileUrlList = projectFileUrlList.slice();
  440. infoMultipleFilesWithGcodeDialog.open();
  441. }
  442. else
  443. {
  444. handleOpenFiles(selectedMultipleFiles, hasProjectFile, fileUrlList, projectFileUrlList);
  445. }
  446. }
  447. function handleOpenFiles(selectedMultipleFiles, hasProjectFile, fileUrlList, projectFileUrlList)
  448. {
  449. // we only allow opening one project file
  450. if (selectedMultipleFiles && hasProjectFile)
  451. {
  452. openFilesIncludingProjectsDialog.fileUrls = fileUrlList.slice();
  453. openFilesIncludingProjectsDialog.show();
  454. return;
  455. }
  456. if (hasProjectFile)
  457. {
  458. var projectFile = projectFileUrlList[0];
  459. // check preference
  460. var choice = UM.Preferences.getValue("cura/choice_on_open_project");
  461. if (choice == "open_as_project")
  462. {
  463. openFilesIncludingProjectsDialog.loadProjectFile(projectFile);
  464. }
  465. else if (choice == "open_as_model")
  466. {
  467. openFilesIncludingProjectsDialog.loadModelFiles([projectFile].slice());
  468. }
  469. else // always ask
  470. {
  471. // ask whether to open as project or as models
  472. askOpenAsProjectOrModelsDialog.fileUrl = projectFile;
  473. askOpenAsProjectOrModelsDialog.show();
  474. }
  475. }
  476. else
  477. {
  478. openFilesIncludingProjectsDialog.loadModelFiles(fileUrlList.slice());
  479. }
  480. }
  481. }
  482. MessageDialog
  483. {
  484. id: packageInstallDialog
  485. title: catalog.i18nc("@window:title", "Install Package");
  486. standardButtons: StandardButton.Ok
  487. modality: Qt.ApplicationModal
  488. }
  489. MessageDialog
  490. {
  491. id: infoMultipleFilesWithGcodeDialog
  492. title: catalog.i18nc("@title:window", "Open File(s)")
  493. icon: StandardIcon.Information
  494. standardButtons: StandardButton.Ok
  495. text: catalog.i18nc("@text:window", "We have found one or more G-Code files within the files you have selected. You can only open one G-Code file at a time. If you want to open a G-Code file, please just select only one.")
  496. property var selectedMultipleFiles
  497. property var hasProjectFile
  498. property var fileUrls
  499. property var projectFileUrlList
  500. onAccepted:
  501. {
  502. openDialog.handleOpenFiles(selectedMultipleFiles, hasProjectFile, fileUrls, projectFileUrlList);
  503. }
  504. }
  505. Connections
  506. {
  507. target: Cura.Actions.open
  508. onTriggered: openDialog.open()
  509. }
  510. OpenFilesIncludingProjectsDialog
  511. {
  512. id: openFilesIncludingProjectsDialog
  513. }
  514. AskOpenAsProjectOrModelsDialog
  515. {
  516. id: askOpenAsProjectOrModelsDialog
  517. }
  518. Connections
  519. {
  520. target: CuraApplication
  521. onOpenProjectFile:
  522. {
  523. askOpenAsProjectOrModelsDialog.fileUrl = project_file;
  524. askOpenAsProjectOrModelsDialog.show();
  525. }
  526. }
  527. Connections
  528. {
  529. target: Cura.Actions.showProfileFolder
  530. onTriggered:
  531. {
  532. var path = UM.Resources.getPath(UM.Resources.Preferences, "");
  533. if(Qt.platform.os == "windows") {
  534. path = path.replace(/\\/g,"/");
  535. }
  536. Qt.openUrlExternally(path);
  537. if(Qt.platform.os == "linux") {
  538. Qt.openUrlExternally(UM.Resources.getPath(UM.Resources.Resources, ""));
  539. }
  540. }
  541. }
  542. AddMachineDialog
  543. {
  544. id: addMachineDialog
  545. onMachineAdded:
  546. {
  547. machineActionsWizard.firstRun = addMachineDialog.firstRun
  548. machineActionsWizard.start(id)
  549. }
  550. }
  551. // Dialog to handle first run machine actions
  552. UM.Wizard
  553. {
  554. id: machineActionsWizard;
  555. title: catalog.i18nc("@title:window", "Add Printer")
  556. property var machine;
  557. function start(id)
  558. {
  559. var actions = Cura.MachineActionManager.getFirstStartActions(id)
  560. resetPages() // Remove previous pages
  561. for (var i = 0; i < actions.length; i++)
  562. {
  563. actions[i].displayItem.reset()
  564. machineActionsWizard.appendPage(actions[i].displayItem, catalog.i18nc("@title", actions[i].label));
  565. }
  566. //Only start if there are actions to perform.
  567. if (actions.length > 0)
  568. {
  569. machineActionsWizard.currentPage = 0;
  570. show()
  571. }
  572. }
  573. }
  574. MessageDialog
  575. {
  576. id: messageDialog
  577. modality: Qt.ApplicationModal
  578. onAccepted: CuraApplication.messageBoxClosed(clickedButton)
  579. onApply: CuraApplication.messageBoxClosed(clickedButton)
  580. onDiscard: CuraApplication.messageBoxClosed(clickedButton)
  581. onHelp: CuraApplication.messageBoxClosed(clickedButton)
  582. onNo: CuraApplication.messageBoxClosed(clickedButton)
  583. onRejected: CuraApplication.messageBoxClosed(clickedButton)
  584. onReset: CuraApplication.messageBoxClosed(clickedButton)
  585. onYes: CuraApplication.messageBoxClosed(clickedButton)
  586. }
  587. Connections
  588. {
  589. target: CuraApplication
  590. onShowMessageBox:
  591. {
  592. messageDialog.title = title
  593. messageDialog.text = text
  594. messageDialog.informativeText = informativeText
  595. messageDialog.detailedText = detailedText
  596. messageDialog.standardButtons = buttons
  597. messageDialog.icon = icon
  598. messageDialog.visible = true
  599. }
  600. }
  601. DiscardOrKeepProfileChangesDialog
  602. {
  603. id: discardOrKeepProfileChangesDialog
  604. }
  605. Connections
  606. {
  607. target: CuraApplication
  608. onShowDiscardOrKeepProfileChanges:
  609. {
  610. discardOrKeepProfileChangesDialog.show()
  611. }
  612. }
  613. Connections
  614. {
  615. target: Cura.Actions.addMachine
  616. onTriggered: addMachineDialog.visible = true;
  617. }
  618. AboutDialog
  619. {
  620. id: aboutDialog
  621. }
  622. Connections
  623. {
  624. target: Cura.Actions.about
  625. onTriggered: aboutDialog.visible = true;
  626. }
  627. Connections
  628. {
  629. target: CuraApplication
  630. onRequestAddPrinter:
  631. {
  632. addMachineDialog.visible = true
  633. addMachineDialog.firstRun = false
  634. }
  635. }
  636. Timer
  637. {
  638. id: startupTimer;
  639. interval: 100;
  640. repeat: false;
  641. running: true;
  642. onTriggered:
  643. {
  644. if(!base.visible)
  645. {
  646. base.visible = true;
  647. }
  648. // check later if the user agreement dialog has been closed
  649. if (CuraApplication.needToShowUserAgreement)
  650. {
  651. restart();
  652. }
  653. else if(Cura.MachineManager.activeMachine == null)
  654. {
  655. addMachineDialog.open();
  656. }
  657. }
  658. }
  659. }