~ubuntu-branches/ubuntu/vivid/gpodder/vivid

« back to all changes in this revision

Viewing changes to share/gpodder/ui/qml/Main.qml

  • Committer: Package Import Robot
  • Author(s): tony mancill
  • Date: 2013-04-12 22:07:02 UTC
  • mfrom: (5.2.27 sid)
  • Revision ID: package-import@ubuntu.com-20130412220702-mux8v9r8zt2jin0x
Tags: 3.5.1-1
* New upstream release.
* d/control: declare versioned dependency on python-gtk2 (>= 2.16)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
 
2
2
import QtQuick 1.1
 
3
 
 
4
import org.gpodder.qmlui 1.0
3
5
import com.nokia.meego 1.0
4
6
 
5
7
import 'config.js' as Config
17
19
        return controller.ntranslate(x, y, z)
18
20
    }
19
21
 
20
 
    property alias podcastModel: podcastList.model
21
 
    property variant episodeModel
22
22
    property alias multiEpisodesSheetOpened: multiEpisodesSheet.opened
23
 
    onEpisodeModelChanged: episodeList.resetFilterDialog()
24
 
    property alias currentEpisode: mediaPlayer.episode
25
 
    property alias showNotesEpisode: showNotes.episode
26
23
    property variant currentPodcast: undefined
27
24
    property bool hasPodcasts: podcastList.hasItems
28
25
    property alias currentFilterText: episodeList.currentFilterText
 
26
    property variant podcastListView: podcastList.listview
29
27
 
30
28
    property bool playing: mediaPlayer.playing
31
 
    property bool canGoBack: (main.state != 'podcasts' || contextMenu.state != 'closed' || mediaPlayer.visible) && !progressIndicator.opacity
32
 
    property bool hasPlayButton: nowPlayingThrobber.shouldAppear && !progressIndicator.opacity
33
 
    property bool hasSearchButton: (contextMenu.state == 'closed' && main.state == 'podcasts') && !mediaPlayer.visible && !progressIndicator.opacity
34
 
    property bool hasFilterButton: state == 'episodes' && !mediaPlayer.visible
 
29
    property bool hasPlayButton: ((mediaPlayer.episode !== undefined)) && !progressIndicator.opacity
 
30
    property bool hasSearchButton: !progressIndicator.opacity
35
31
 
36
32
    property bool loadingEpisodes: false
37
33
 
38
34
    function clearEpisodeListModel() {
39
 
        loadingEpisodes = true
40
 
        startProgress(_('Loading episodes'))
41
 
    }
42
 
 
43
 
    function setEpisodeListModel(model) {
 
35
        /* Abort loading when clearing list model */
 
36
        episodeListModelLoader.running = false;
 
37
        loadingEpisodes = true;
44
38
        episodeListModel.clear();
45
 
        for (var i=0; i<model.length; i++) {
46
 
            episodeListModel.append(model[i]);
 
39
    }
 
40
 
 
41
    Timer {
 
42
        id: episodeListModelLoader
 
43
 
 
44
        /**
 
45
         * These values determined by non-scientific experimentation,
 
46
         * feel free to tweak depending on the power of your device.
 
47
         *
 
48
         * Loads <stepSize> items every <interval> ms, and to populate
 
49
         * the first screen, loads <initialStepSize> items on start.
 
50
         **/
 
51
        property int initialStepSize: 13
 
52
        property int stepSize: 4
 
53
        interval: 50
 
54
 
 
55
        property int count: 0
 
56
        property int position: 0
 
57
 
 
58
        repeat: true
 
59
        triggeredOnStart: true
 
60
 
 
61
        onTriggered: {
 
62
            var step = (position === 0) ? initialStepSize : stepSize;
 
63
            var end = Math.min(count, position+step);
 
64
 
 
65
            for (var i=position; i<end; i++) {
 
66
                episodeListModel.append(episodeModel.get_object_by_index(i));
 
67
            }
 
68
 
 
69
            position = end;
 
70
            if (position === count) {
 
71
                running = false;
 
72
                main.loadingEpisodes = false;
 
73
            } else if (pageStack.depth === 1) {
 
74
                /* Abort loading when switching to main view */
 
75
                running = false;
 
76
                main.loadingEpisodes = false;
 
77
            }
47
78
        }
48
 
        loadingEpisodes = false
49
 
        endProgress()
 
79
    }
 
80
 
 
81
    function setEpisodeListModel() {
 
82
        episodeListModelLoader.count = episodeModel.getCount();
 
83
        episodeListModelLoader.position = 0;
 
84
        episodeListModelLoader.restart();
 
85
    }
 
86
 
 
87
    Component.onCompleted: {
 
88
        /* Signal connections for upcalls from the backend */
 
89
        controller.episodeUpdated.connect(episodeUpdated);
 
90
 
 
91
        controller.showMessage.connect(showMessage);
 
92
        controller.showInputDialog.connect(showInputDialog);
 
93
        controller.openContextMenu.connect(openContextMenu);
 
94
 
 
95
        controller.startProgress.connect(startProgress);
 
96
        controller.endProgress.connect(endProgress);
 
97
 
 
98
        controller.clearEpisodeListModel.connect(clearEpisodeListModel);
 
99
        controller.setEpisodeListModel.connect(setEpisodeListModel);
 
100
 
 
101
        controller.enqueueEpisode.connect(enqueueEpisode);
 
102
        controller.removeQueuedEpisode.connect(removeQueuedEpisode);
 
103
        controller.removeQueuedEpisodesForPodcast.connect(removeQueuedEpisodesForPodcast);
 
104
 
 
105
        controller.shutdown.connect(shutdown);
50
106
    }
51
107
 
52
108
    function episodeUpdated(id) {
67
123
    }
68
124
 
69
125
    function clickSearchButton() {
70
 
        contextMenu.showSubscribe()
 
126
        pageStack.push(subscribePage);
71
127
    }
72
128
 
73
 
    function goBack() {
74
 
        if (nowPlayingThrobber.opened) {
75
 
            clickPlayButton()
76
 
        } else if (contextMenu.state == 'opened') {
77
 
            contextMenu.state = 'closed'
78
 
        } else if (main.state == 'podcasts') {
79
 
            mediaPlayer.stop()
80
 
            controller.quit()
81
 
        } else if (main.state == 'episodes') {
82
 
            main.state = 'podcasts'
83
 
            main.currentPodcast = undefined
84
 
        } else if (main.state == 'shownotes') {
85
 
            main.state = 'episodes'
86
 
        }
 
129
    function shutdown() {
 
130
        mediaPlayer.stop();
87
131
    }
88
132
 
89
133
    function showFilterDialog() {
91
135
    }
92
136
 
93
137
    function clickPlayButton() {
94
 
        nowPlayingThrobber.opened = !nowPlayingThrobber.opened
 
138
        if (!main.hasPlayButton) {
 
139
            main.showMessage(_('Playlist empty'));
 
140
            return;
 
141
        }
 
142
 
 
143
        if (pageStack.currentPage === mediaPlayerPage) {
 
144
            pageStack.pop();
 
145
        } else {
 
146
            pageStack.push(mediaPlayerPage);
 
147
        }
95
148
    }
96
149
 
97
150
    function showMultiEpisodesSheet(title, label, action) {
99
152
        multiEpisodesSheet.acceptButtonText = label;
100
153
        multiEpisodesSheet.action = action;
101
154
        multiEpisodesList.selected = [];
 
155
        multiEpisodesList.contentY = episodeList.listViewContentY;
102
156
        multiEpisodesSheet.open();
103
157
        multiEpisodesSheet.opened = true;
104
158
    }
106
160
    width: 800
107
161
    height: 480
108
162
 
109
 
    state: 'podcasts'
110
 
 
111
163
    function enqueueEpisode(episode) {
112
 
        if (currentEpisode === undefined) {
 
164
        if (mediaPlayer.episode === undefined) {
113
165
            togglePlayback(episode);
114
166
        } else {
115
167
            mediaPlayer.enqueueEpisode(episode);
136
188
    }
137
189
 
138
190
    function openShowNotes(episode) {
139
 
        showNotes.episode = episode
140
 
        main.state = 'shownotes'
 
191
        showNotes.episode = episode;
 
192
        pageStack.push(showNotesPage);
141
193
    }
142
194
 
143
195
    function openContextMenu(items) {
154
206
        progressIndicator.opacity = 0
155
207
    }
156
208
 
157
 
    states: [
158
 
        State {
159
 
            name: 'podcasts'
160
 
            PropertyChanges {
161
 
                target: podcastList
162
 
                opacity: 1
163
 
            }
164
 
            PropertyChanges {
165
 
                target: episodeList
166
 
                anchors.leftMargin: 100
167
 
                opacity: 0
168
 
            }
169
 
            PropertyChanges {
170
 
                target: showNotes
171
 
                opacity: 0
172
 
            }
173
 
            StateChangeScript {
174
 
                script: episodeList.resetSelection()
175
 
            }
176
 
        },
177
 
        State {
178
 
            name: 'episodes'
179
 
            PropertyChanges {
180
 
                target: episodeList
181
 
                opacity: !main.loadingEpisodes
182
 
            }
183
 
            PropertyChanges {
184
 
                target: podcastList
185
 
                opacity: 0
186
 
                anchors.leftMargin: -100
187
 
            }
188
 
            PropertyChanges {
189
 
                target: showNotes
190
 
                opacity: 0
191
 
                anchors.leftMargin: main.width
192
 
            }
193
 
        },
194
 
        State {
195
 
            name: 'shownotes'
196
 
            PropertyChanges {
197
 
                target: listContainer
198
 
                opacity: 0
199
 
            }
200
 
            PropertyChanges {
201
 
                target: showNotes
202
 
                opacity: 1
203
 
                anchors.leftMargin: 0
204
 
            }
205
 
        }
206
 
    ]
 
209
    PodcastList {
 
210
        id: podcastList
 
211
        model: podcastModel
207
212
 
208
 
    Item {
209
 
        id: listContainer
210
213
        anchors.fill: parent
211
214
 
212
 
        PodcastList {
213
 
            id: podcastList
214
 
            opacity: 0
215
 
 
216
 
            anchors.fill: parent
217
 
 
218
 
            onPodcastSelected: {
219
 
                controller.podcastSelected(podcast)
220
 
                main.currentPodcast = podcast
221
 
            }
222
 
            onPodcastContextMenu: controller.podcastContextMenu(podcast)
223
 
            onSubscribe: contextMenu.showSubscribe()
224
 
 
225
 
            Behavior on opacity { NumberAnimation { duration: Config.slowTransition } }
226
 
            Behavior on anchors.leftMargin { NumberAnimation { duration: Config.slowTransition } }
 
215
        onPodcastSelected: {
 
216
            controller.podcastSelected(podcast);
 
217
            main.currentPodcast = podcast;
 
218
            pageStack.push(episodesPage);
 
219
        }
 
220
        onPodcastContextMenu: controller.podcastContextMenu(podcast)
 
221
        onSubscribe: pageStack.push(subscribePage);
 
222
    }
 
223
 
 
224
    PagePage {
 
225
        id: episodesPage
 
226
        lockToPortrait: mainPage.lockToPortrait
 
227
        listview: episodeList.listview
 
228
 
 
229
        onClosed: {
 
230
            episodeList.resetSelection();
 
231
            main.currentPodcast = undefined;
227
232
        }
228
233
 
229
234
        EpisodeList {
230
235
            id: episodeList
231
 
            mainState: main.state
 
236
 
 
237
            anchors.fill: parent
232
238
 
233
239
            model: ListModel { id: episodeListModel }
234
 
 
235
 
            opacity: 0
236
 
 
237
 
            anchors.fill: parent
238
 
 
239
240
            onEpisodeContextMenu: controller.episodeContextMenu(episode)
240
 
 
241
 
            Behavior on opacity { NumberAnimation { duration: Config.slowTransition } }
242
 
            Behavior on anchors.leftMargin { NumberAnimation { duration: Config.slowTransition } }
243
 
        }
244
 
 
245
 
        Behavior on opacity { NumberAnimation { duration: Config.slowTransition } }
246
 
        Behavior on scale { NumberAnimation { duration: Config.fadeTransition } }
247
 
    }
248
 
 
249
 
    ShowNotes {
250
 
        id: showNotes
251
 
 
252
 
        anchors {
253
 
            left: parent.left
254
 
            top: parent.top
255
 
            bottom: parent.bottom
256
 
        }
257
 
        width: parent.width
258
 
 
259
 
        Behavior on opacity { NumberAnimation { duration: Config.slowTransition } }
260
 
        Behavior on anchors.leftMargin { NumberAnimation { duration: Config.slowTransition } }
 
241
        }
 
242
 
 
243
        actions: [
 
244
            Action {
 
245
                text: _('Now playing')
 
246
                onClicked: {
 
247
                    main.clickPlayButton();
 
248
                }
 
249
            },
 
250
            Action {
 
251
                text: _('Filter:') + ' ' + mainObject.currentFilterText
 
252
                onClicked: {
 
253
                    mainObject.showFilterDialog();
 
254
                }
 
255
            },
 
256
            Action {
 
257
                text: _('Download episodes')
 
258
                onClicked: {
 
259
                    main.showMultiEpisodesSheet(text, _('Download'), 'download');
 
260
                }
 
261
            },
 
262
            Action {
 
263
                text: _('Playback episodes')
 
264
                onClicked: {
 
265
                    main.showMultiEpisodesSheet(text, _('Play'), 'play');
 
266
                }
 
267
            },
 
268
            Action {
 
269
                text: _('Delete episodes')
 
270
                onClicked: {
 
271
                    main.showMultiEpisodesSheet(text, _('Delete'), 'delete');
 
272
                }
 
273
            }
 
274
        ]
 
275
 
261
276
    }
262
277
 
263
278
    Item {
264
279
        id: overlayInteractionBlockWall
265
280
        anchors.fill: parent
266
 
        z: (contextMenu.state != 'opened')?2:0
 
281
        z: 2
267
282
 
268
 
        opacity: (nowPlayingThrobber.opened || contextMenu.state == 'opened' || messageDialog.opacity || inputDialog.opacity || progressIndicator.opacity)?1:0
 
283
        opacity: (inputDialog.opacity || progressIndicator.opacity)?1:0
269
284
        Behavior on opacity { NumberAnimation { duration: Config.slowTransition } }
270
285
 
271
286
        MouseArea {
272
287
            anchors.fill: parent
273
288
            onClicked: {
274
 
                if (contextMenu.state == 'opened') {
275
 
                    // do nothing
276
 
                } else if (progressIndicator.opacity) {
 
289
                if (progressIndicator.opacity) {
277
290
                    // do nothing
278
291
                } else if (inputDialog.opacity) {
279
292
                    inputDialog.close()
280
 
                } else if (messageDialog.opacity) {
281
 
                    messageDialog.opacity = 0
282
 
                } else {
283
 
                    nowPlayingThrobber.opened = false
284
293
                }
285
294
            }
286
295
        }
297
306
        }
298
307
    }
299
308
 
300
 
    Item {
301
 
        // XXX: Remove me
302
 
        id: nowPlayingThrobber
303
 
        property bool shouldAppear: ((contextMenu.state != 'opened') && (mediaPlayer.episode !== undefined))
304
 
        property bool opened: false
305
 
    }
306
 
 
307
 
    MediaPlayer {
308
 
        id: mediaPlayer
309
 
        visible: nowPlayingThrobber.opened
310
 
 
311
 
        z: 3
312
 
 
313
 
        anchors.top: parent.bottom
314
 
        anchors.left: parent.left
315
 
        anchors.right: parent.right
316
 
        anchors.topMargin: nowPlayingThrobber.opened?-(height+(parent.height-height)/2):0
317
 
 
318
 
        Behavior on anchors.topMargin { PropertyAnimation { duration: Config.quickTransition; easing.type: Easing.OutCirc } }
 
309
    PagePage {
 
310
        id: mediaPlayerPage
 
311
        lockToPortrait: mainPage.lockToPortrait
 
312
 
 
313
        MediaPlayer {
 
314
            id: mediaPlayer
 
315
 
 
316
            anchors {
 
317
                left: parent.left
 
318
                right: parent.right
 
319
                verticalCenter: parent.verticalCenter
 
320
            }
 
321
        }
 
322
 
 
323
        actions: [
 
324
            Action {
 
325
                text: _('Shownotes')
 
326
                onClicked: main.openShowNotes(mediaPlayer.episode)
 
327
            },
 
328
 
 
329
            Action {
 
330
                text: _('Play queue')
 
331
                onClicked: {
 
332
                    if (mediaPlayer.hasQueue) {
 
333
                        mediaPlayer.showQueue();
 
334
                    } else {
 
335
                        main.showMessage(_('Playlist empty'));
 
336
                    }
 
337
                }
 
338
            }
 
339
        ]
319
340
    }
320
341
 
321
342
    ContextMenu {
337
358
        }
338
359
    }
339
360
 
340
 
    ContextMenuArea {
341
 
        id: contextMenu
342
 
 
343
 
        width: parent.width
344
 
        opacity: 0
345
 
 
346
 
        anchors {
347
 
            top: parent.top
348
 
            bottom: parent.bottom
349
 
        }
350
 
 
351
 
        onClose: contextMenu.state = 'closed'
352
 
        onResponse: controller.contextMenuResponse(index)
353
 
 
354
 
        state: 'closed'
355
 
 
356
 
        Behavior on opacity { NumberAnimation { duration: Config.fadeTransition } }
357
 
 
358
 
        states: [
359
 
            State {
360
 
                name: 'opened'
361
 
                PropertyChanges {
362
 
                    target: contextMenu
363
 
                    opacity: 1
364
 
                }
365
 
                AnchorChanges {
366
 
                    target: contextMenu
367
 
                    anchors.right: main.right
368
 
                }
369
 
            },
370
 
            State {
371
 
                name: 'closed'
372
 
                PropertyChanges {
373
 
                    target: contextMenu
374
 
                    opacity: 0
375
 
                }
376
 
                AnchorChanges {
377
 
                    target: contextMenu
378
 
                    anchors.right: main.left
379
 
                }
380
 
                StateChangeScript {
381
 
                    script: controller.contextMenuClosed()
382
 
                }
383
 
            }
384
 
        ]
385
 
 
386
 
        transitions: Transition {
387
 
            AnchorAnimation { duration: Config.slowTransition }
388
 
        }
389
 
    }
390
 
 
391
361
    function showMessage(message) {
392
 
        messageDialogText.text = message
393
 
        messageDialog.opacity = 1
394
 
    }
395
 
 
396
 
    Item {
397
 
        id: messageDialog
398
 
        anchors.fill: parent
399
 
        opacity: 0
400
 
        z: 20
401
 
 
402
 
        Behavior on opacity { PropertyAnimation { } }
403
 
 
404
 
        Label {
405
 
            id: messageDialogText
406
 
            anchors {
407
 
                left: parent.left
408
 
                right: parent.right
409
 
                verticalCenter: parent.verticalCenter
410
 
                leftMargin: Config.largeSpacing
411
 
                rightMargin: Config.largeSpacing
412
 
            }
413
 
            color: 'white'
414
 
            font.pixelSize: 20
415
 
            font.bold: true
416
 
            width: parent.width
417
 
            horizontalAlignment: Text.AlignHCenter
418
 
            wrapMode: Text.WordWrap
419
 
        }
 
362
        infoBanner.text = message;
 
363
        infoBanner.show();
420
364
    }
421
365
 
422
366
    function showInputDialog(message, value, accept, reject, textInput) {
451
395
        property bool opened: false
452
396
        property string title: ''
453
397
        acceptButtonText: _('Delete')
454
 
        visualParent: episodeList
455
398
        anchors.fill: parent
456
399
        anchors.topMargin: -36
457
400
 
506
449
                }
507
450
            }
508
451
 
509
 
            ScrollDecorator { flickableItem: multiEpisodesList }
 
452
            ScrollScroll { flickable: multiEpisodesList }
510
453
 
511
454
            ContextMenu {
512
455
                id: multiEpisodesSheetContextMenu
528
471
                        onClicked: {
529
472
                            var newSelection = [];
530
473
                            for (var i=0; i<multiEpisodesList.count; i++) {
531
 
                                if (main.episodeModel.get_object_by_index(i).downloaded) {
 
474
                                if (episodeModel.get_object_by_index(i).downloaded) {
532
475
                                    newSelection.push(i);
533
476
                                }
534
477
                            }
563
506
    Sheet {
564
507
        id: inputSheet
565
508
 
 
509
        anchors.fill: parent
 
510
        anchors.topMargin: -50
 
511
 
566
512
        acceptButtonText: inputDialogAccept.text
567
513
        rejectButtonText: inputDialogReject.text
568
514