Qrop.qml 19.2 KB
Newer Older
1
/*
André Hoarau's avatar
André Hoarau committed
2
 * Copyright (C) 2018-2020 André Hoarau <ah@ouvaton.org>
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 3.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

André Hoarau's avatar
André Hoarau committed
17
import QtQuick 2.10
18
import QtQuick.Controls 2.4
19
import QtQuick.Layouts 1.3
20
import QtQuick.Controls.Material 2.3
21
22
import Qt.labs.settings 1.0
import Qt.labs.platform 1.0 as Platform
23
import QtQuick.Window 2.12
24

André Hoarau's avatar
André Hoarau committed
25
import io.qrop.components 1.0
26

27
28
29
ApplicationWindow {
    id: window

30
    readonly property int minLoadingDisplaySize: 100
31
    readonly property bool largeDisplay: width > 100
32
    readonly property bool smallDisplay: width < 500
André Hoarau's avatar
André Hoarau committed
33
    property bool railMode: width > 1200
34
35
    property bool searchMode: false
    property bool showSaveButton: false
36

André Hoarau's avatar
André Hoarau committed
37
38
39
40
    property string firstDatabaseFile: settings.firstDatabaseFile
    property string secondDatabaseFile: settings.secondDatabaseFile
    property int currentDatabase: settings.currentDatabase
    property string lastFolder: settings.lastFolder
41

42
43
44
45
46
47
48
49
    property int navigationIndex: 0
    property var navigationModel: [
        { loader: plantingsPage, name: qsTr("Plantings"), iconText: "\ue0b8", component: "PlantingsPage.qml" },
        { loader: calendarPage,  name: qsTr("Tasks"),     iconText: "\ue614", component: "CalendarPage.qml" },
        { loader: locationsPage, name: qsTr("Crop Map"),  iconText: "\ue55b", component: "LocationsPage.qml" },
        { loader: harvestsPage,  name: qsTr("Harvests"),  iconText: "\ue896", component: "HarvestsPage.qml" },
        { loader: seedListPage,  name: qsTr("Seed list"), iconText: "\ue8ef", component: "SeedsPage.qml" },
        { loader: chartsPage,    name: qsTr("Charts"),    iconText: "\ue801", component: "ChartsPage.qml" },
André Hoarau's avatar
André Hoarau committed
50
//        { loader: notesPage,     name: qsTr("Notes"),     iconText: "\ue616", component: "NotesPage.qml" },
51
52
53
54
        { loader: settingsPage,  name: qsTr("Settings"),  iconText: "\ue616", component: "SettingsPage.qml",
          bindings: { "paneWidth": Math.min(600, stackLayout.width * 0.8) }}
    ]

55
56
57
58
59
60
61
62
63
64
65
    enum DB_ACTION {
        OPEN = 0,
        NEW  = 1,
        SAVE = 2
    }

    property bool modifyMainDatabase: true
    property int dbAction: Qrop.DB_ACTION.OPEN

    property int popupTimeout : 8000

66
67
68
69
70
71
    Component.onCompleted: {
        if (firstDatabaseFile === "")
            firstDatabaseFile = Database.defaultDatabasePathUrl();
        if (currentDatabase === 0)
            currentDatabase = 1;
        if (lastFolder === "")
72
            lastFolder = '%1%2'.arg(BuildInfo.isMobileDevice() ? "" : "file://").arg(FileSystem.rootPath);
73
74
75
76
77

        if (mainSettings.windowFullScreen)
            window.visibility = Window.FullScreen;
        else {
            window.visibility = Window.Windowed;
78
79
80
81
82
83
84
            if (mainSettings.windowWidth < minLoadingDisplaySize) {
                window.x = Screen.width*1/8;
                window.width = Screen.width*3/4;
            } else {
                window.x = mainSettings.windowX;
                window.width = mainSettings.windowWidth;
            }
85
            window.y = mainSettings.windowY;
86
            window.height = mainSettings.windowHeight < minLoadingDisplaySize ? Screen.height : mainSettings.windowHeight;
87
88
89
90
91
92
93
94
95
96
        }
    }

    Component.onDestruction: {
        if (!mainSettings.windowFullScreen) {
            mainSettings.windowX = window.x;
            mainSettings.windowY = window.y;
            mainSettings.windowHeight = window.height;
            mainSettings.windowWidth = window.width;
        }
97
98
    }

99
100
    function toggleFullScreen() {
        if (window.visibility === Window.FullScreen) {
101
102
103
104
105
106
            window.visibility = Window.Windowed;
            mainSettings.windowFullScreen = false;
            window.x = mainSettings.windowX;
            window.y = mainSettings.windowY;
            window.height = mainSettings.windowHeight;
            window.width = mainSettings.windowWidth;
107
        } else {
108
109
110
111
112
113
            mainSettings.windowX = window.x;
            mainSettings.windowY = window.y;
            mainSettings.windowHeight = window.height;
            mainSettings.windowWidth = window.width;
            mainSettings.windowFullScreen = true;
            window.visibility = Window.FullScreen;
114
115
116
        }
    }

117
    function switchToNextPane() {
André Hoarau's avatar
André Hoarau committed
118
        if (navigationIndex === navigationModel.length) {
119
            navigationIndex = 0;
André Hoarau's avatar
André Hoarau committed
120
        } else {
121
            navigationIndex++;
André Hoarau's avatar
André Hoarau committed
122
        }
123
124
125
    }

    function switchToPreviousPane() {
André Hoarau's avatar
André Hoarau committed
126
        if (navigationIndex === 0) {
127
            navigationIndex = navigationModel.length;
André Hoarau's avatar
André Hoarau committed
128
        } else {
129
            navigationModel--;
André Hoarau's avatar
André Hoarau committed
130
        }
131
132
133
    }

    function switchToDatabase(db) {
André Hoarau's avatar
André Hoarau committed
134
        Database.connectToDatabase(window.currentDatabase === 1 ? firstDatabaseFile : secondDatabaseFile)
135
136
137
        if (locationsPage.item) {
            locationsPage.item.reload();
        }
138
        stackLayout.currentItem.refresh();
139
    }
140
    
141
142
143
    function openDatabaseActionDialog(action, mainDB) {
        dbAction = action;
        modifyMainDatabase = mainDB;
144
145
        if (BuildInfo.isMobileDevice()){
            if (action === Qrop.DB_ACTION.OPEN) {
146
                let availableDBs = FileSystem.getAvailableDataBasesNames();
147
148
149
150
                if (availableDBs.length === 0)
                    error(qsTr('There are no database available...'),
                          '%1: <b>%2</b>'.arg(
                              qsTr("They should all be copied in the following folder")).arg(
151
                              FileSystem.rootPath));
152
153
154
                else {
//                    for (var i=0; i < availableDBs.length; ++i)
//                        print("[MB_TRACE] Available DB: "+availableDBs[i]);
155
156
157
                    databaseMobileDialog.nameField.visible = false;
                    databaseMobileDialog.combo.visible = true;
                    databaseMobileDialog.combo.model = availableDBs;
158
159
160
161
162
                    databaseMobileDialog.title = modifyMainDatabase ?
                                qsTr('Open Main DataBase') : qsTr('Open Secondary DataBase');
                    databaseMobileDialog.text = '%1<br/>%2 %3'.arg(
                                qsTr("Please select a database to open")).arg(
                                qsTr("They must all be in the folder:")).arg(
163
                                FileSystem.rootPath);
164
165
                    databaseMobileDialog.open();
                }
166
            } else {
167
168
                databaseMobileDialog.nameField.visible = true;
                databaseMobileDialog.combo.visible = false;
169
170
171
172
173
174
175
176
177
178
                if (action === Qrop.DB_ACTION.NEW) {
                    databaseMobileDialog.title = modifyMainDatabase ?
                                qsTr('New Main DataBase') : qsTr('New Secondary DataBase');
                } else {
                    databaseMobileDialog.title = modifyMainDatabase ?
                                qsTr('Export Main DataBase') : qsTr('Export Secondary DataBase');
                }
                databaseMobileDialog.text = qsTr("Database name:");
                databaseMobileDialog.open();
            }
179
        } else {
180
181
182
183
            databaseDialog.fileMode = action === Qrop.DB_ACTION.OPEN ?
                        Platform.FileDialog.OpenFile : Platform.FileDialog.SaveFile;
            databaseDialog.open();
        }
184
    }
185
186
187

    function doDatabaseAction(file){
//        print("DB Action: "+dbAction);
188
        if (dbAction == Qrop.DB_ACTION.SAVE) { // NOT === just a double == !!!
189
            Database.copy(modifyMainDatabase ? firstDatabaseFile : secondDatabaseFile, file);
190
        } else {
191
192
193
194
195
196
197
198
199
200
            if (modifyMainDatabase) {
                firstDatabaseFile = file;
                currentDatabase = 1;
            } else {
                secondDatabaseFile = file;
                currentDatabase = 2;
            }
            switchToDatabase();
        }
    } // doDatabaseAction
201
    
André Hoarau's avatar
André Hoarau committed
202
203
204
205
206
207
    function info(text) {
        infoSnackbar.text = text;
        infoSnackbar.open();
    }

    function error(text) {
208
        info("<font color='darkred'>%1</font>".arg(text));
André Hoarau's avatar
André Hoarau committed
209
210
    }

211
212
213
214
    title: "Qrop"
    visible: true
    width: 1024
    height: 768
215
216
    flags: Qt.Window

André Hoarau's avatar
André Hoarau committed
217
218
    Material.primary: Material.color(Material.Teal, Material.Shade500)
    Material.accent: Material.color(Material.Blue, Material.Shade600)
219

220
221
222
223
224
    Settings {
        id: mainSettings
        property bool useStandardBedLength
        property int standardBedLength
        property bool showPlantingSuccessionNumber
225
226
227
228
229
230

        property bool windowFullScreen
        property int windowX
        property int windowY
        property int windowHeight
        property int windowWidth
231
232
    }

233
234
    Settings {
        id: settings
235
236
237
238
        property alias firstDatabaseFile: window.firstDatabaseFile
        property alias secondDatabaseFile: window.secondDatabaseFile
        property alias currentDatabase: window.currentDatabase
        property alias lastFolder: window.lastFolder
239
240
    }

241
242
243
244
245
246
247
    ApplicationShortcut { sequence: StandardKey.Quit; onActivated: Qt.quit() }
    ApplicationShortcut { sequence: "Ctrl+1"; onActivated: navigationIndex = 0 }
    ApplicationShortcut { sequence: "Ctrl+2"; onActivated: navigationIndex = 1 }
    ApplicationShortcut { sequence: "Ctrl+3"; onActivated: navigationIndex = 2 }
    ApplicationShortcut { sequence: "Ctrl+4"; onActivated: navigationIndex = 3 }
    ApplicationShortcut { sequence: "Ctrl+5"; onActivated: navigationIndex = 4 }
    ApplicationShortcut { sequence: "Ctrl+6"; onActivated: navigationIndex = 5 }
André Hoarau's avatar
André Hoarau committed
248
//    ApplicationShortcut { sequence: "Ctrl+7"; onActivated: navigationIndex = 6 }
249
250
251
252
253
    ApplicationShortcut { sequence: "Ctrl+0"; onActivated: navigationIndex = navigationModel.length - 1 }
    ApplicationShortcut { sequence: StandardKey.NextChild; onActivated: switchToNextPane() }
    ApplicationShortcut { sequence: StandardKey.PreviousChild; onActivated: switchToPreviousPane() }
    ApplicationShortcut { sequence: "F11"; onActivated: toggleFullScreen() }

254
255
256
257
258
259
260
    Drawer {
        id: drawer
        height: window.height
        modal: !largeDisplay
        interactive: !largeDisplay
        position: largeDisplay ? 1 : 0
        visible: largeDisplay
André Hoarau's avatar
André Hoarau committed
261
        Material.background: Material.primary
262

263
        ColumnLayout {
264
            anchors.fill: parent
André Hoarau's avatar
André Hoarau committed
265
            spacing: 0
266

267
            Repeater {
268
269
                // We remove the last item because Settings icon is a the bottom of the Drawer.
                model: navigationModel.slice(0, -1)
270
271

                DrawerItemDelegate {
272
                    Layout.fillWidth: true
273
                    width: drawer.width
274
275
                    text: modelData.name
                    iconText: modelData.iconText
276
                    isActive: index === navigationIndex
277
278
                    onClicked: {
                        navigationIndex = index
André Hoarau's avatar
André Hoarau committed
279
                        if (!largeDisplay)
280
281
                            drawer.close()
                    }
282
283
                }
            }
284
285

            Item { Layout.fillHeight: true }
André Hoarau's avatar
André Hoarau committed
286

André Hoarau's avatar
André Hoarau committed
287
288
289
290
            DrawerItemDelegate {
                id: mainDatabase
                Layout.fillWidth: true
                width: drawer.width
André Hoarau's avatar
André Hoarau committed
291
                text: Helpers.urlBaseName(firstDatabaseFile)
André Hoarau's avatar
André Hoarau committed
292
                iconText: "\ue400"
André Hoarau's avatar
André Hoarau committed
293
294
295
296
297
298
299
300
301
                isActive: currentDatabase === 1
                showToolTip: false

                onClicked: {
                    if (currentDatabase !== 1) {
                        currentDatabase = 1;
                        switchToDatabase();
                    }
                }
André Hoarau's avatar
André Hoarau committed
302

André Hoarau's avatar
André Hoarau committed
303
                onPressAndHold: mainDatabaseMenu.open();
André Hoarau's avatar
André Hoarau committed
304
305
306
307
308
309
310

                Menu {
                    id: mainDatabaseMenu
                    title: qsTr("Main database menu")
                    x: parent.width
                    margins: 0

André Hoarau's avatar
André Hoarau committed
311
312
                    MenuItem {
                        text: qsTr("New...");
313
                        onTriggered: openDatabaseActionDialog(Qrop.DB_ACTION.NEW, true);
André Hoarau's avatar
André Hoarau committed
314
315
316
                    }
                    MenuItem {
                        text: qsTr("Open...");
317
                        onTriggered: openDatabaseActionDialog(Qrop.DB_ACTION.OPEN, true);
André Hoarau's avatar
André Hoarau committed
318
319
320
                    }
                    MenuItem {
                        text: qsTr("Export...");
321
                        onTriggered: openDatabaseActionDialog(Qrop.DB_ACTION.SAVE, true);
André Hoarau's avatar
André Hoarau committed
322
323
324
325
326
327
328
329
                    }
                }

                ToolTip {
                    text: firstDatabaseFile
                    visible: parent.hovered
                    x: parent.width + Units.smallSpacing
                    y: height/4
André Hoarau's avatar
André Hoarau committed
330
331
332
333
334
335
336
                }
            }

            DrawerItemDelegate {
                id: secondDatabaseButton
                Layout.fillWidth: true
                width: drawer.width
André Hoarau's avatar
André Hoarau committed
337
                showToolTip: false
André Hoarau's avatar
André Hoarau committed
338
                iconText: "\ue401"
André Hoarau's avatar
André Hoarau committed
339
                text: secondDatabaseFile === "" ? "" : Helpers.urlBaseName(secondDatabaseFile)
André Hoarau's avatar
André Hoarau committed
340
341
342
343
344
                iconColor: secondDatabaseFile === ""
                           ?  Material.color(Material.Grey, Material.Shade400)
                           : "white"

                isActive: currentDatabase === 2
André Hoarau's avatar
André Hoarau committed
345
346

                onClicked: {
André Hoarau's avatar
André Hoarau committed
347
348
349
350
351
352
353
354
                    if (currentDatabase !== 2) {
                        if (secondDatabaseFile !== "") {
                            currentDatabase = 2
                            switchToDatabase();
                        } else {
                            databaseMenu.open();
                        }
                    }
André Hoarau's avatar
André Hoarau committed
355
356
                }

André Hoarau's avatar
André Hoarau committed
357
                onPressAndHold: databaseMenu.open();
André Hoarau's avatar
André Hoarau committed
358

André Hoarau's avatar
André Hoarau committed
359
360
361
362
363
364
365
                ToolTip {
                    text: secondDatabaseFile == "" ? qsTr("No other database opened")
                                                   : secondDatabaseFile
                    visible: parent.hovered
                    x: parent.width + Units.smallSpacing
                    y: height/4
                }
André Hoarau's avatar
André Hoarau committed
366
367
368
369
370
371
372
373

                Menu {
                    id: databaseMenu
                    title: qsTr("Database menu")
                    x: parent.width
                    margins: 0

                    MenuItem {
André Hoarau's avatar
André Hoarau committed
374
                        text: qsTr("New...");
375
                        onTriggered: openDatabaseActionDialog(Qrop.DB_ACTION.NEW, false);
André Hoarau's avatar
André Hoarau committed
376
377
378
                    }
                    MenuItem {
                        text: qsTr("Open...");
379
                        onTriggered: openDatabaseActionDialog(Qrop.DB_ACTION.OPEN, false);
André Hoarau's avatar
André Hoarau committed
380
381
382
383
                    }
                    MenuItem {
                        text: qsTr("Export...")
                        enabled: secondDatabaseFile !== ""
384
                        onTriggered: openDatabaseActionDialog(Qrop.DB_ACTION.SAVE, false);
André Hoarau's avatar
André Hoarau committed
385
386
387
                    }
                    MenuItem {
                        text: qsTr("Close");
André Hoarau's avatar
André Hoarau committed
388
                        enabled: secondDatabaseFile !== ""
André Hoarau's avatar
André Hoarau committed
389
390
391
392
393
394
395
                        onTriggered: {
                            if (currentDatabase === 2) {
                                currentDatabase = 1;
                                switchToDatabase();
                            }
                            secondDatabaseFile = ""
                        }
André Hoarau's avatar
André Hoarau committed
396
397
398
399
                    }
                }
            }

André Hoarau's avatar
André Hoarau committed
400
            DrawerItemDelegate {
André Hoarau's avatar
André Hoarau committed
401
                id: settingsDrawerButton
André Hoarau's avatar
André Hoarau committed
402
                Layout.fillWidth: true
403
                width: drawer.width
André Hoarau's avatar
André Hoarau committed
404
                text: qsTr("Settings")
405
406
                iconText: "\ue8b8"
                isActive: navigationModel.length == navigationIndex
André Hoarau's avatar
André Hoarau committed
407
408

                onClicked: {
409
                    navigationIndex = navigationModel.length - 1
André Hoarau's avatar
André Hoarau committed
410
                    if (!largeDisplay)
André Hoarau's avatar
André Hoarau committed
411
412
413
                        drawer.close()
                }
            }
André Hoarau's avatar
André Hoarau committed
414
415
416
417
418
419
420
421
422

            DrawerItemDelegate {
                id: aboutDrawerDelegate
                Layout.fillWidth: true
                width: drawer.width
                text: qsTr("About")
                iconText: "\ue887"
                isActive: false

André Hoarau's avatar
André Hoarau committed
423
                onClicked: aboutDialog.open();
André Hoarau's avatar
André Hoarau committed
424
            }
425
426
427
        }
    }

428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
    Rectangle {
        id: busyRectangle
        visible: stackLayout.isLoading
        width: parent.width
        height: Units.toolBarHeight
        Material.elevation: 2

        ThinDivider {
            id: topDivider
            anchors.top: parent.bottom
            width: parent.width
        }

        ProgressBar {
            anchors { top: topDivider.bottom; left: parent.left; right: parent.right }
            indeterminate: true
444
445
446
        }
    }

447
448
449
450
    StackLayout {
        id: stackLayout

        property bool isLoading: children[currentIndex].status === Loader.Loading
451
        property var currentItem: children[currentIndex].item
452

André Hoarau's avatar
André Hoarau committed
453
        focus: true
454
455
        anchors.fill: parent
        anchors.leftMargin: largeDisplay ? drawer.width : undefined
André Hoarau's avatar
André Hoarau committed
456
        anchors.rightMargin: 0
457
        currentIndex: navigationIndex
André Hoarau's avatar
André Hoarau committed
458

459
460
461
462
        onCurrentIndexChanged: {
            let index = currentIndex;
            if (index >= navigationModel.length) {
                return;
André Hoarau's avatar
André Hoarau committed
463
464
            }

465
466
467
468
            if (navigationModel[index].loader.status === Loader.Null) {
                if (navigationModel[index].bindings) {
                    navigationModel[index].loader.setSource(navigationModel[index].component,
                                                      navigationModel[index].bindings);
André Hoarau's avatar
André Hoarau committed
469
                } else {
470
                    navigationModel[index].loader.setSource(navigationModel[index].component);
471
                }
472
473
            } else {
                navigationModel[index].loader.item.refresh();
474
            }
475
        }
476
477
478
479
480
481
482

        Loader { id: plantingsPage; source: "PlantingsPage.qml"; asynchronous: true }
        Loader { id: calendarPage; asynchronous: true }
        Loader { id: locationsPage; asynchronous: true  }
        Loader { id: harvestsPage; asynchronous: true  }
        Loader { id: seedListPage; asynchronous: true  }
        Loader { id: chartsPage; asynchronous: true  }
André Hoarau's avatar
André Hoarau committed
483
484
//        Loader { id: notesPage; asynchronous: true  }
        Loader { id: settingsPage; asynchronous: false  }
485
486
    }

André Hoarau's avatar
André Hoarau committed
487
    AboutDialog {
488
489
        id: aboutDialog
        x: (window.width - width) / 2
André Hoarau's avatar
André Hoarau committed
490
491
        y: (window.height - height) / 2
        width: 500
492
    }
493

André Hoarau's avatar
André Hoarau committed
494
495
496
497
498
499
500
    Snackbar {
        id: infoSnackbar
        z: 2
        x: drawer.width + Units.mediumSpacing
        y: parent.height - height - Units.mediumSpacing
        visible: false
    }
501
502
503
504
505
506
507
508

    Platform.FileDialog {
        id: databaseDialog

        defaultSuffix: "sqlite"
        folder: Qt.resolvedUrl(window.lastFolder)
        nameFilters: [("SQLite (*.db *.sqlite)")]
        onAccepted: {
509
510
            doDatabaseAction(file)
            lastFolder = folder.toString();
511
512
513
        }
    } // databaseDialog

514
    MobileFileDialog {
515
516
517
518
519
520
        id: databaseMobileDialog

        x: (window.width - width) / 2
        y: (window.height - height) / 2

        onAccepted: {
521
            //MB_TODO: check if the file already exist? shall we overwrite or discard?
522
523
            let dbName = dbAction == Qrop.DB_ACTION.OPEN ? combo.currentText : nameField.text;
            doDatabaseAction('file://%1/%2.sqlite'.arg(FileSystem.rootPath).arg(dbName));
524
        }
525
    }
526
}