2
* Copyright (C) 2013 Canonical Ltd
4
* This program is free software: you can redistribute it and/or modify
5
* it under the terms of the GNU General Public License version 3 as
6
* published by the Free Software Foundation.
8
* This program is distributed in the hope that it will be useful,
9
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
* GNU General Public License for more details.
13
* You should have received a copy of the GNU General Public License
14
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16
* Authored by: Arto Jalkanen <ajalkane@gmail.com>
19
import Ubuntu.Components 0.1
20
import Ubuntu.Components.Popups 0.1
21
import Ubuntu.Components.ListItems 0.1
22
import org.nemomobile.folderlistmodel 1.0
27
title: folderName(folder)
29
property variant fileView: folderListPage
31
property bool showHiddenFiles: false
33
property bool showingListView: folderListView.visible
35
onShowHiddenFilesChanged: {
36
pageModel.showHiddenFiles = folderListPage.showHiddenFiles
39
property string sortingMethod: "Name"
41
onSortingMethodChanged: {
42
console.log("Sorting by: " + sortingMethod)
43
if (sortingMethod === "Name") {
44
pageModel.sortBy = FolderListModel.SortByName
45
} else if (sortingMethod === "Date") {
46
pageModel.sortBy = FolderListModel.SortByDate
48
// Something fatal happened!
49
console.log("ERROR: Invalid sort type:", sortingMethod)
53
property bool sortAccending: true
55
onSortAccendingChanged: {
56
console.log("Sorting accending: " + sortAccending)
59
pageModel.sortOrder = FolderListModel.SortAscending
61
pageModel.sortOrder = FolderListModel.SortDescending
65
// This stores the location using ~ to represent home
66
property string folder
67
property string homeFolder: "~"
69
// This replaces ~ with the actual home folder, since the
70
// plugin doesn't recognize the ~
71
property string path: folder.replace("~", pageModel.homePath())
74
goTo(folderListPage.homeFolder)
77
function goTo(location) {
78
// Since the FolderListModel returns paths using the actual
79
// home folder, this replaces with ~ before actually going
80
// to the specified folder
81
while (location !== '/' && location.substring(location.lastIndexOf('/')+1) === "") {
82
location = location.substring(0, location.length - 1)
85
folderListPage.folder = location.replace(pageModel.homePath(), "~")
93
function pathAccessedDate() {
94
console.log("calling method pageModel.curPathAccessedDate()")
95
return pageModel.curPathAccessedDate()
98
function pathModifiedDate() {
99
console.log("calling method pageModel.curPathModifiedDate()")
100
return pageModel.curPathModifiedDate()
103
function pathIsWritable() {
104
console.log("calling method pageModel.curPathIsWritable()")
105
return pageModel.curPathIsWritable()
108
// FIXME: hard coded path for icon, assumes Ubuntu desktop icon available.
109
// Nemo mobile has icon provider. Have to figure out what's the proper way
110
// to get "system wide" icons in Ubuntu Touch, or if we have to use
111
// icons packaged into the application. Both folder and individual
112
// files will need an icon.
113
// TODO: Remove isDir parameter and use new model functions
114
function fileIcon(file, isDir) {
115
file = file.replace(pageModel.homePath(), "~")
116
var iconPath = isDir ? "/usr/share/icons/Humanity/places/48/folder.svg"
117
: "/usr/share/icons/Humanity/mimes/48/empty.svg"
120
iconPath = "icons/folder-home.svg"
121
} else if (file === i18n.tr("~/Desktop")) {
122
iconPath = "/usr/share/icons/Humanity/places/48/user-desktop.svg"
123
} else if (file === i18n.tr("~/Documents")) {
124
iconPath = "/usr/share/icons/Humanity/places/48/folder-documents.svg"
125
} else if (file === i18n.tr("~/Downloads")) {
126
iconPath = "/usr/share/icons/Humanity/places/48/folder-downloads.svg"
127
} else if (file === i18n.tr("~/Music")) {
128
iconPath = "/usr/share/icons/Humanity/places/48/folder-music.svg"
129
} else if (file === i18n.tr("~/Pictures")) {
130
iconPath = "/usr/share/icons/Humanity/places/48/folder-pictures.svg"
131
} else if (file === i18n.tr("~/Public")) {
132
iconPath = "/usr/share/icons/Humanity/places/48/folder-publicshare.svg"
133
} else if (file === i18n.tr("~/Programs")) {
134
iconPath = "/usr/share/icons/Humanity/places/48/folder-system.svg"
135
} else if (file === i18n.tr("~/Templates")) {
136
iconPath = "/usr/share/icons/Humanity/places/48/folder-templates.svg"
137
} else if (file === i18n.tr("~/Videos")) {
138
iconPath = "/usr/share/icons/Humanity/places/48/folder-videos.svg"
139
} else if (file === "/") {
140
iconPath = "/usr/share/icons/Humanity/devices/48/drive-harddisk.svg"
143
return Qt.resolvedUrl(iconPath)
146
function folderName(folder) {
147
folder = folder.replace(pageModel.homePath(), "~")
149
if (folder === folderListPage.homeFolder) {
150
return i18n.tr("Home")
151
} else if (folder === "/") {
152
return i18n.tr("File System")
154
return folder.substr(folder.lastIndexOf('/') + 1)
158
function pathName(folder) {
159
if (folder === "/") {
162
return folder.substr(folder.lastIndexOf('/') + 1)
166
function pathExists(path) {
167
path = path.replace("~", pageModel.homePath())
172
if(path.charAt(0) === '/') {
173
console.log("Directory: " + path.substring(0, path.lastIndexOf('/')+1))
174
repeaterModel.path = path.substring(0, path.lastIndexOf('/')+1)
175
console.log("Sub dir: " + path.substring(path.lastIndexOf('/')+1))
176
if (path.substring(path.lastIndexOf('/')+1) !== "" && !repeaterModel.cdIntoPath(path.substring(path.lastIndexOf('/')+1))) {
186
property bool loading: pageModel.awaitingResults
191
path: folderListPage.path
193
enableExternalFSWatcher: true
195
// Properties to emulate a model entry for use by FileDetailsPopover
196
property bool isDir: true
197
property string fileName: pathName(pageModel.path)
198
property string fileSize: (folderListView.count === 1
200
: i18n.tr("%1 files").arg(folderListView.count))
201
property bool isReadable: true
202
property bool isExecutable: true
207
path: folderListPage.folder
210
console.log("Path: " + repeaterModel.path)
216
ActionSelectionPopover {
217
objectName: "tabsPopover"
221
grabDismissAreaEvents: true
223
actions: ActionList {
225
text: i18n.tr("Open in a new tab")
227
openTab(folderListPage.folder)
231
// The current tab can be closed as long as there is at least one tab remaining
233
text: i18n.tr("Close this tab")
237
enabled: tabs.count > 1
244
id: folderActionsPopoverComponent
245
ActionSelectionPopover {
246
id: folderActionsPopover
247
objectName: "folderActionsPopover"
249
grabDismissAreaEvents: true
251
actions: ActionList {
253
text: i18n.tr("Create New Folder")
257
PopupUtils.open(createFolderDialog, folderListPage)
261
// TODO: Disabled until backend supports creating files
263
// text: i18n.tr("Create New File")
267
// PopupUtils.open(createFileDialog, root)
272
text: pageModel.clipboardUrlsCounter === 0
274
: pageModel.clipboardUrlsCounter === 1
275
? i18n.tr("Paste %1 File").arg(pageModel.clipboardUrlsCounter)
276
: i18n.tr("Paste %1 Files").arg(pageModel.clipboardUrlsCounter)
278
console.log("Pasting to current folder items of count " + pageModel.clipboardUrlsCounter)
279
fileOperationDialog.startOperation(i18n.tr("Paste files"))
283
// FIXME: This property is depreciated and doesn't seem to work!
284
//visible: pageModel.clipboardUrlsCounter > 0
286
enabled: pageModel.clipboardUrlsCounter > 0
289
// TODO: Disabled until support for opening apps is added
291
text: i18n.tr("Open in Terminal")
295
// Is this the way it will work??
296
Qt.openUrlExternally("app://terminal")
299
enabled: showAdvancedFeatures && false
303
text: i18n.tr("Properties")
306
PopupUtils.open(Qt.resolvedUrl("FileDetailsPopover.qml"),
318
id: createFolderDialog
319
ConfirmDialogWithInput {
320
title: i18n.tr("Create folder")
321
text: i18n.tr("Enter name for new folder")
324
console.log("Create folder accepted", inputText)
325
if (inputText !== '') {
326
pageModel.mkdir(inputText)
328
console.log("Empty directory name, ignored")
336
ConfirmDialogWithInput {
337
title: i18n.tr("Create file")
338
text: i18n.tr("Enter name for new file")
341
console.log("Create file accepted", inputText)
342
if (inputText !== '') {
343
//FIXME: Actually create a new file!
345
console.log("Empty file name, ignored")
351
function openFile(filePath) {
352
if (!pageModel.openPath(filePath)) {
353
error(i18n.tr("File operation error"), i18n.tr("Unable to open '%11").arg(filePath))
357
tools: ToolbarItems {
362
onLockedChanged: opened = locked
364
back: ToolbarButton {
367
iconSource: getIcon("keyboard-caps")
368
enabled: folder != "/"
370
goTo(pageModel.parentPath)
376
width: folderListPage.width - units.gu(37)
378
anchors.verticalCenter: parent.verticalCenter
381
width: Math.min(pathItem.width, implicitWidth)
382
visible: sidebar.expanded
388
height: parent.height
393
objectName: "actions"
394
text: i18n.tr("Actions")
395
iconSource: getIcon("navigation-menu")
399
PopupUtils.open(folderActionsPopoverComponent, actionsButton)
404
text: i18n.tr("View")
405
iconSource: getIcon("properties")
411
PopupUtils.open(Qt.resolvedUrl("ViewPopover.qml"), optionsButton)
417
visible: !sidebar.expanded
419
text: i18n.tr("Places")
420
iconSource: getIcon("location")
424
PopupUtils.open(Qt.resolvedUrl("PlacesPopover.qml"), placesButton)
431
text: i18n.tr("Tabs")
432
iconSource: getIcon("browser-tabs")
437
PopupUtils.open(tabsPopover, tabsButton, {
438
tab: folderListPage.parent
445
visible: sidebar.expanded
446
objectName: "settings"
447
action: settingsAction
451
flickable: !sidebar.expanded ? folderListView.visible ? folderListView : folderIconView.flickable : null
453
onFlickableChanged: {
454
if (flickable === null) {
455
folderListView.topMargin = 0
456
folderIconView.flickable.topMargin = 0
458
folderListView.topMargin = units.gu(9.5)
459
folderIconView.flickable.topMargin = units.gu(9.5)
465
objectName: "placesSidebar"
469
// bottom: parent.bottom
470
// bottomMargin: units.gu(-2)
473
expanded: showSidebar
481
folderListModel: pageModel
484
bottom: parent.bottom
488
smallMode: !sidebar.expanded
489
visible: viewMethod === i18n.tr("Icons")
497
folderListModel: pageModel
500
bottom: parent.bottom
504
smallMode: !sidebar.expanded
505
visible: viewMethod === i18n.tr("List")
513
bottom: parent.bottom
520
text: i18n.tr("No files")
523
anchors.centerIn: parent
524
visible: folderListView.count == 0 && !pageModel.awaitingResults
528
running: pageModel.awaitingResults
531
anchors.centerIn: parent
536
id: confirmSingleDeleteDialog
538
property string filePath
539
property string fileName
540
title: i18n.tr("Delete")
541
text: i18n.tr("Are you sure you want to permanently delete '%1'?").arg(fileName)
544
console.log("Delete accepted for filePath, fileName", filePath, fileName)
546
fileOperationDialog.startOperation("Deleting files")
547
console.log("Doing delete")
548
pageModel.rm(filePath)
554
id: confirmRenameDialog
555
ConfirmDialogWithInput {
556
// IMPROVE: this does not seem good: the backend excepts row and new name.
557
// But what if new files are added/deleted in the background while user is
558
// entering the new name? The indices change and wrong file is renamed.
559
// Perhaps the backend should take as parameters the "old name" and "new name"?
560
// This is not currently a problem since the backend does not poll changes in
561
// the filesystem, but may be a problem in the future.
562
property int modelRow
564
title: i18n.tr("Rename")
565
text: i18n.tr("Enter a new name")
568
console.log("Rename accepted", inputText)
569
if (inputText !== '') {
570
console.log("Rename commensed, modelRow/inputText", modelRow, inputText)
571
if (pageModel.rename(modelRow, inputText) === false) {
572
PopupUtils.open(Qt.resolvedUrl("NotifyDialog.qml"), delegate,
574
title: i18n.tr("Could not rename"),
575
text: i18n.tr("Insufficient permissions or name already exists?")
580
console.log("Empty new name given, ignored")
587
id: actionSelectionPopoverComponent
589
ActionSelectionPopover {
590
id: actionSelectionPopover
591
objectName: "fileActionsPopover"
593
grabDismissAreaEvents: true
596
actions: ActionList {
600
iconSource: "/usr/share/icons/Humanity/actions/48/edit-cut.svg"
602
console.log("Cut on row called for", actionSelectionPopover.model.fileName, actionSelectionPopover.model.index)
603
pageModel.cutIndex(actionSelectionPopover.model.index)
604
console.log("CliboardUrlsCounter after copy", pageModel.clipboardUrlsCounter)
609
text: i18n.tr("Copy")
611
iconSource: "/usr/share/icons/Humanity/actions/48/edit-copy.svg"
614
console.log("Copy on row called for", actionSelectionPopover.model.fileName, actionSelectionPopover.model.index)
615
pageModel.copyIndex(actionSelectionPopover.model.index)
616
console.log("CliboardUrlsCounter after copy", pageModel.clipboardUrlsCounter)
621
text: i18n.tr("Delete")
623
iconSource: "/usr/share/icons/Humanity/actions/48/edit-delete.svg"
626
PopupUtils.open(confirmSingleDeleteDialog, actionSelectionPopover.caller,
627
{ "filePath" : actionSelectionPopover.model.filePath,
628
"fileName" : actionSelectionPopover.model.fileName }
634
text: i18n.tr("Rename")
636
iconSource: "/usr/share/icons/Humanity/actions/48/rotate.svg"
639
PopupUtils.open(confirmRenameDialog, actionSelectionPopover.caller,
640
{ "modelRow" : actionSelectionPopover.model.index,
641
"inputText" : actionSelectionPopover.model.fileName
647
text: i18n.tr("Properties")
650
PopupUtils.open(Qt.resolvedUrl("FileDetailsPopover.qml"),
651
actionSelectionPopover.caller,
652
{ "model": actionSelectionPopover.model
665
console.log("FolderListModel Error Title/Description", errorTitle, errorMessage)
666
error(i18n.tr("File operation error"), errorTitle + ": " + errorMessage)
670
FileOperationProgressDialog {
671
id: fileOperationDialog
677
function itemClicked(model) {
679
if (model.isReadable && model.isExecutable) {
680
console.log("Changing to dir", model.filePath)
683
PopupUtils.open(Qt.resolvedUrl("NotifyDialog.qml"), delegate,
685
title: i18n.tr("Folder not accessible"),
686
text: i18n.tr("Can not access ") + model.fileName
691
console.log("Non dir clicked")
692
openFile(model.fileName)
693
// PopupUtils.open(Qt.resolvedUrl("FileActionDialog.qml"), root,
695
// fileName: model.fileName,
696
// filePath: model.filePath,
697
// folderListModel: root.folderListModel
702
function itemLongPress(delegate, model) {
703
console.log("FolderListDelegate onPressAndHold")
704
PopupUtils.open(actionSelectionPopoverComponent, delegate,
710
function keyPressed(key, modifiers) {
711
if (key === Qt.Key_L && modifiers & Qt.ControlModifier) {
712
PopupUtils.open(Qt.resolvedUrl("GoToDialog.qml"), goToButton)
719
Component.onCompleted: forceActiveFocus()