QML: Settings, about box, graphics update (bug 1554)
[gpodder.git] / share / gpodder / ui / qml / Main.qml
blob151faf43da5e970a3ddfe28ffbf0393354edcf56
2 import Qt 4.7
3 import com.nokia.meego 1.0
5 import 'config.js' as Config
7 Image {
8     id: main
9     focus: true
11     function _(x) {
12         return controller.translate(x)
13     }
15     function n_(x, y, z) {
16         return controller.ntranslate(x, y, z)
17     }
19     property alias podcastModel: podcastList.model
20     property alias episodeModel: episodeList.model
21     property alias currentEpisode: mediaPlayer.episode
22     property variant currentPodcast: undefined
23     property bool hasPodcasts: podcastList.hasItems
24     property alias currentFilterText: episodeList.currentFilterText
26     property bool playing: mediaPlayer.playing
27     property bool canGoBack: (closeButton.isRequired || mediaPlayer.visible) && !progressIndicator.opacity
28     property bool hasPlayButton: nowPlayingThrobber.shouldAppear && !progressIndicator.opacity
29     property bool hasSearchButton: searchButton.visible && !mediaPlayer.visible && !progressIndicator.opacity
30     property bool hasFilterButton: state == 'episodes' && !mediaPlayer.visible
32     function goBack() {
33         if (nowPlayingThrobber.opened) {
34             nowPlayingThrobber.opened = false
35         } else {
36             closeButton.clicked()
37         }
38     }
40     function showFilterDialog() {
41         episodeList.showFilterDialog()
42     }
44     function clickPlayButton() {
45         nowPlayingThrobber.clicked()
46     }
48     function clickSearchButton() {
49         searchButton.clicked()
50     }
52     Keys.onPressed: {
53         console.log(event.key)
54         if (event.key == Qt.Key_Escape) {
55             goBack()
56         }
57         if (event.key == Qt.Key_F && event.modifiers & Qt.ControlModifier) {
58             searchButton.clicked()
59         }
60     }
62     width: 800
63     height: 480
65     property bool useEmptyBackground: !podcastList.hasItems
67     anchors.topMargin: useEmptyBackground?-35:0
68     fillMode: useEmptyBackground?Image.Tile:Image.Stretch
69     source: {
70         if (useEmptyBackground) {
71             '/usr/share/themes/blanco/meegotouch/images/backgrounds/meegotouch-empty-application-background-black-portrait.png'
72         } else {
73             'artwork/background-harmattan.png'
74         }
75     }
77     state: 'podcasts'
79     function enqueueEpisode(episode) {
80         if (currentEpisode === undefined) {
81             togglePlayback(episode);
82         } else {
83             mediaPlayer.enqueueEpisode(episode);
84         }
85     }
87     function removeQueuedEpisodesForPodcast(podcast) {
88         mediaPlayer.removeQueuedEpisodesForPodcast(podcast);
89     }
91     function removeQueuedEpisode(episode) {
92         mediaPlayer.removeQueuedEpisode(episode);
93     }
95     function togglePlayback(episode) {
96         if (episode !== undefined && episode.qfiletype == 'video') {
97             controller.playVideo(episode)
98         } else {
99             mediaPlayer.togglePlayback(episode)
100         }
101     }
103     function openShowNotes(episode) {
104         showNotes.episode = episode
105         main.state = 'shownotes'
106     }
108     function openContextMenu(items) {
109         hrmtnContextMenu.items = items
110         hrmtnContextMenu.open()
111     }
113     function startProgress(text) {
114         progressIndicator.text = text
115         progressIndicator.opacity = 1
116     }
118     function endProgress() {
119         progressIndicator.opacity = 0
120     }
122     states: [
123         State {
124             name: 'podcasts'
125             PropertyChanges {
126                 target: podcastList
127                 opacity: 1
128             }
129             PropertyChanges {
130                 target: episodeList
131                 anchors.leftMargin: 100
132                 opacity: 0
133             }
134             PropertyChanges {
135                 target: showNotes
136                 opacity: 0
137             }
138             StateChangeScript {
139                 script: episodeList.resetSelection()
140             }
141         },
142         State {
143             name: 'episodes'
144             PropertyChanges {
145                 target: episodeList
146                 opacity: 1
147             }
148             PropertyChanges {
149                 target: podcastList
150                 opacity: 0
151                 anchors.leftMargin: -100
152             }
153             PropertyChanges {
154                 target: showNotes
155                 opacity: 0
156                 anchors.leftMargin: main.width
157             }
158         },
159         State {
160             name: 'shownotes'
161             PropertyChanges {
162                 target: listContainer
163                 opacity: 0
164             }
165             PropertyChanges {
166                 target: showNotes
167                 opacity: 1
168                 anchors.leftMargin: 0
169             }
170         }
171     ]
173     Item {
174         id: listContainer
175         anchors.fill: parent
176         anchors.topMargin: titleBar.height
178         PodcastList {
179             id: podcastList
180             opacity: 0
182             anchors.fill: parent
184             onPodcastSelected: {
185                 controller.podcastSelected(podcast)
186                 main.currentPodcast = podcast
187             }
188             onPodcastContextMenu: controller.podcastContextMenu(podcast)
189             onSubscribe: contextMenu.showSubscribe()
191             Behavior on opacity { NumberAnimation { duration: Config.slowTransition } }
192             Behavior on anchors.leftMargin { NumberAnimation { duration: Config.slowTransition } }
193         }
195         EpisodeList {
196             id: episodeList
198             opacity: 0
200             anchors.fill: parent
202             onEpisodeContextMenu: controller.episodeContextMenu(episode)
204             Behavior on opacity { NumberAnimation { duration: Config.slowTransition } }
205             Behavior on anchors.leftMargin { NumberAnimation { duration: Config.slowTransition } }
206         }
208         Behavior on opacity { NumberAnimation { duration: Config.slowTransition } }
209         Behavior on scale { NumberAnimation { duration: Config.fadeTransition } }
210     }
212     ShowNotes {
213         id: showNotes
215         anchors {
216             left: parent.left
217             top: titleBar.bottom
218             bottom: parent.bottom
219         }
220         width: parent.width
222         Behavior on opacity { NumberAnimation { duration: Config.slowTransition } }
223         Behavior on anchors.leftMargin { NumberAnimation { duration: Config.slowTransition } }
224     }
226     Item {
227         id: overlayInteractionBlockWall
228         anchors.fill: parent
229         anchors.topMargin: (nowPlayingThrobber.opened || messageDialog.opacity > 0 || inputDialog.opacity > 0 || progressIndicator.opacity > 0)?0:titleBar.height
230         z: (contextMenu.state != 'opened')?2:0
232         opacity: (nowPlayingThrobber.opened || contextMenu.state == 'opened' || messageDialog.opacity || inputDialog.opacity || progressIndicator.opacity)?1:0
233         Behavior on opacity { NumberAnimation { duration: Config.slowTransition } }
235         MouseArea {
236             anchors.fill: parent
237             onClicked: {
238                 if (contextMenu.state == 'opened') {
239                     // do nothing
240                 } else if (progressIndicator.opacity) {
241                     // do nothing
242                 } else if (inputDialog.opacity) {
243                     inputDialog.close()
244                 } else if (messageDialog.opacity) {
245                     messageDialog.opacity = 0
246                 } else {
247                     nowPlayingThrobber.opened = false
248                 }
249             }
250         }
252         Rectangle {
253             anchors.fill: parent
254             color: 'black'
255             opacity: .7
256         }
258         Image {
259             anchors.fill: parent
260             source: 'artwork/mask.png'
261         }
262     }
264     CornerButton {
265         id: extraCloseButton
266         visible: false
267         z: (contextMenu.state == 'opened')?2:0
268         tab: 'artwork/back-tab.png'
269         icon: 'artwork/back.png'
270         isLeftCorner: true
271         anchors.bottom: parent.bottom
272         anchors.left: parent.left
273         onClicked: closeButton.clicked()
274         opened: !(!Config.hasCloseButton && closeButton.isRequired)
275     }
277     CornerButton {
278         z: 3
280         property bool shouldAppear: ((contextMenu.state != 'opened') && (mediaPlayer.episode !== undefined))
282         id: nowPlayingThrobber
283         visible: false
284         anchors.bottom: parent.bottom
285         anchors.right: parent.right
286         opacity: shouldAppear
288         caption: (mediaPlayer.episode!=undefined)?mediaPlayer.episode.qtitle:''
290         opened: false
291         onClicked: { opened = !opened }
292     }
294     MediaPlayer {
295         id: mediaPlayer
296         visible: nowPlayingThrobber.opened
298         z: 3
300         anchors.top: parent.bottom
301         anchors.left: parent.left
302         anchors.right: parent.right
303         anchors.topMargin: nowPlayingThrobber.opened?-(height+(parent.height-height)/2):0
305         Behavior on anchors.topMargin { PropertyAnimation { duration: Config.quickTransition; easing.type: Easing.OutCirc } }
306     }
308     ContextMenu {
309         id: hrmtnContextMenu
310         property variant items: []
312         MenuLayout {
313             Repeater {
314                 model: hrmtnContextMenu.items
316                 MenuItem {
317                     text: modelData.caption
318                     onClicked: {
319                         hrmtnContextMenu.close()
320                         controller.contextMenuResponse(index)
321                     }
322                 }
323             }
324         }
325     }
327     ContextMenuArea {
328         id: contextMenu
330         width: parent.width
331         opacity: 0
333         anchors {
334             top: parent.top
335             bottom: parent.bottom
336         }
338         onClose: contextMenu.state = 'closed'
339         onResponse: controller.contextMenuResponse(index)
341         state: 'closed'
343         Behavior on opacity { NumberAnimation { duration: Config.fadeTransition } }
345         states: [
346             State {
347                 name: 'opened'
348                 PropertyChanges {
349                     target: contextMenu
350                     opacity: 1
351                 }
352                 AnchorChanges {
353                     target: contextMenu
354                     anchors.right: main.right
355                 }
356             },
357             State {
358                 name: 'closed'
359                 PropertyChanges {
360                     target: contextMenu
361                     opacity: 0
362                 }
363                 AnchorChanges {
364                     target: contextMenu
365                     anchors.right: main.left
366                 }
367                 StateChangeScript {
368                     script: controller.contextMenuClosed()
369                 }
370             }
371         ]
373         transitions: Transition {
374             AnchorAnimation { duration: Config.slowTransition }
375         }
376     }
378     Item {
379         id: titleBar
380         visible: podcastList.hasItems
381         height: visible?taskSwitcher.height*.8:0
382         anchors.left: parent.left
383         anchors.right: parent.right
384         anchors.top: parent.top
386         //anchors.topMargin: mediaPlayer.fullscreen?-height:0
387         //opacity: mediaPlayer.fullscreen?0:1
389         Behavior on opacity { PropertyAnimation { } }
390         Behavior on anchors.topMargin { PropertyAnimation { } }
392         Rectangle {
393             anchors.fill: parent
394             color: "black"
395             opacity: .9
397             MouseArea {
398                 // clicks should not fall through!
399                 anchors.fill: parent
400             }
401         }
403         Item {
404             id: taskSwitcher
405             visible: contextMenu.state != 'opened' && Config.hasTaskSwitcher
406             anchors.left: parent.left
407             anchors.top: parent.top
408             width: Config.switcherWidth
409             height: Config.headerHeight
411             MouseArea {
412                 anchors.fill: parent
413                 onClicked: controller.switcher()
414             }
416             ScaledIcon {
417                 anchors {
418                     verticalCenter: parent.verticalCenter
419                     left: parent.left
420                     leftMargin: (parent.width * .8 - width) / 2
421                 }
422                 source: 'artwork/switch.png'
423             }
424         }
426         Label {
427             id: titleBarText
428             anchors.verticalCenter: parent.verticalCenter
429             anchors.left: taskSwitcher.visible?taskSwitcher.right:taskSwitcher.left
430             anchors.leftMargin: (contextMenu.state == 'opened')?(Config.largeSpacing):(Config.hasTaskSwitcher?0:Config.largeSpacing)
431             anchors.right: searchButton.visible?searchButton.left:searchButton.right
432             wrapMode: Text.NoWrap
433             clip: true
434             text: (contextMenu.state == 'opened')?(contextMenu.subscribeMode?_('Add a new podcast'):_('Context menu')):((main.state == 'episodes' || main.state == 'shownotes')?(controller.episodeListTitle + ' (' + episodeList.count + ')'):"gPodder")
435             color: 'white'
436             font.pixelSize: parent.height * .5
437             font.bold: false
438         }
440         Binding {
441             target: controller
442             property: 'windowTitle'
443             value: titleBarText.text
444         }
446         TitlebarButton {
447             id: searchButton
448             anchors.right: closeButton.visible?closeButton.left:closeButton.right
450             source: 'artwork/subscriptions.png'
452             onClicked: contextMenu.showSubscribe()
454             visible: (contextMenu.state == 'closed' && main.state == 'podcasts')
455             opacity: 0
456         }
458         TitlebarButton {
459             id: closeButton
460             anchors.right: parent.right
461             property bool isRequired: main.state != 'podcasts' || contextMenu.state != 'closed'
462             visible: extraCloseButton.opened && (Config.hasCloseButton || isRequired)
464             source: (main.state == 'podcasts' && contextMenu.state == 'closed')?'artwork/close.png':'artwork/back.png'
465             rotation: 0
467             onClicked: {
468                 if (contextMenu.state == 'opened') {
469                     contextMenu.state = 'closed'
470                 } else if (main.state == 'podcasts') {
471                     mediaPlayer.stop()
472                     controller.quit()
473                 } else if (main.state == 'episodes') {
474                     main.state = 'podcasts'
475                     main.currentPodcast = undefined
476                 } else if (main.state == 'shownotes') {
477                     main.state = 'episodes'
478                 }
479             }
480         }
481     }
483     function showMessage(message) {
484         messageDialogText.text = message
485         messageDialog.opacity = 1
486     }
488     Item {
489         id: messageDialog
490         anchors.fill: parent
491         opacity: 0
492         z: 20
494         Behavior on opacity { PropertyAnimation { } }
496         Label {
497             id: messageDialogText
498             anchors.centerIn: parent
499             color: 'white'
500             font.pixelSize: 20
501             font.bold: true
502         }
503     }
505     function showInputDialog(message, value, accept, reject, textInput) {
506         inputDialogText.text = message
507         inputDialogField.text = value
508         inputDialogAccept.text = accept
509         inputDialogReject.text = reject
510         inputDialogField.visible = textInput
512         if (textInput) {
513             inputSheet.open()
514         } else {
515             queryDialog.open()
516         }
517     }
519     QueryDialog {
520         id: queryDialog
522         acceptButtonText: inputDialogAccept.text
523         rejectButtonText: inputDialogReject.text
525         message: inputDialogText.text
527         onAccepted: inputDialog.accept()
528         onRejected: inputDialog.close()
529     }
531     Sheet {
532         id: inputSheet
534         acceptButtonText: inputDialogAccept.text
535         rejectButtonText: inputDialogReject.text
537         content: Item {
538             anchors.fill: parent
540             MouseArea {
541                 anchors.fill: parent
542                 onClicked: console.log('caught')
543             }
545             Column {
546                 anchors.fill: parent
547                 anchors.margins: Config.smallSpacing
548                 spacing: Config.smallSpacing
550                 Item {
551                     height: 1
552                     width: parent.width
553                 }
555                 Label {
556                     id: inputDialogText
557                     anchors.margins: Config.smallSpacing
558                     width: parent.width
559                 }
561                 Item {
562                     height: 1
563                     width: parent.width
564                 }
566                 InputField {
567                     id: inputDialogField
568                     width: parent.width
569                     onAccepted: {
570                         inputDialog.accept()
571                         inputSheet.close()
572                     }
573                     actionName: inputDialogAccept.text
574                 }
575             }
576         }
578         onAccepted: inputDialog.accept()
579         onRejected: inputDialog.close()
580     }
582     Item {
583         id: inputDialog
584         anchors.fill: parent
585         opacity: 0
587         function accept() {
588             opacity = 0
589             scale = .5
590             controller.inputDialogResponse(true, inputDialogField.text,
591                                            inputDialogField.visible)
592         }
594         function close() {
595             opacity = 0
596             scale = .5
597             controller.inputDialogResponse(false, inputDialogField.text,
598                                            inputDialogField.visible)
599         }
601         SimpleButton {
602             id: inputDialogReject
603             width: parent.width / 2
604             onClicked: inputDialog.close()
605         }
607         SimpleButton {
608             id: inputDialogAccept
609             width: parent.width / 2
610             onClicked: inputDialog.accept()
611         }
612     }
614     Column {
615         id: progressIndicator
616         property string text: '...'
617         anchors.centerIn: parent
618         opacity: 0
619         spacing: Config.largeSpacing * 2
620         z: 40
622         Behavior on opacity { NumberAnimation { duration: Config.slowTransition } }
624         Label {
625             text: parent.text
626             anchors.horizontalCenter: parent.horizontalCenter
627         }
629         BusyIndicator {
630             anchors.horizontalCenter: parent.horizontalCenter
631             running: parent.opacity > 0
632         }
633     }