]> code.delx.au - notipod/blob - notipod_gui.py
Whitespace
[notipod] / notipod_gui.py
1 #!/usr/bin/env python
2 # Copyright 2009 James Bunton <jamesbunton@fastmail.fm>
3 # Licensed for distribution under the GPL version 2, check COPYING for details
4
5 import logging
6
7 import objc
8 from Foundation import *
9 from AppKit import *
10 from PyObjCTools import AppHelper
11
12 import libnotipod
13
14
15 class PlaylistModel(NSObject):
16 outlineView = objc.IBOutlet()
17
18 def awakeFromNib(self):
19 self.root = []
20 self.playlists = {}
21 self.outlineView.setDataSource_(self)
22
23 def setPlaylists(self, playlists):
24 self.root = []
25 self.playlists = playlists
26 for playlist in self.playlists:
27 if playlist.parent is None:
28 self.root.append(playlist)
29 self.outlineView.reloadData()
30 self.outlineView.expandItem_expandChildren_(None, True)
31
32 def outlineView_child_ofItem_(self, _, childIndex, playlist):
33 if playlist == None:
34 return self.root[childIndex]
35 else:
36 return playlist.children[childIndex]
37
38 def outlineView_isItemExpandable_(self, _, playlist):
39 if playlist == None:
40 return True
41 else:
42 return len(playlist.children) > 0
43
44 def outlineView_numberOfChildrenOfItem_(self, _, playlist):
45 if playlist == None:
46 return len(self.root)
47 else:
48 return len(playlist.children)
49
50 def outlineView_objectValueForTableColumn_byItem_(self, _, col, playlist):
51 col = col.identifier() if col else "playlist"
52
53 if col == "selected":
54 selected = NSApp.delegate().playlists()
55 return playlist.pid in selected
56 if col == None or col == "playlist":
57 return playlist.name
58
59 def outlineView_setObjectValue_forTableColumn_byItem_(self, _, v, col, playlist):
60 col = col.identifier() if col else "playlist"
61
62 if col != "selected":
63 return
64 NSApp.delegate().setPlaylist_selected_(playlist.pid, v)
65
66
67 class FolderModel(NSObject):
68 window = objc.IBOutlet()
69 folderPopup = objc.IBOutlet()
70
71 def awakeFromNib(self):
72 folders = NSApp.delegate().folders()
73 self.folderPopup.addItemsWithTitles_(folders)
74 if len(folders) > 0:
75 self.folderPopup.selectItemAtIndex_(2)
76 self.lastIndex = 2
77 else:
78 self.lastIndex = 0
79
80 @objc.IBAction
81 def doSelectFolder_(self, sender):
82 currentIndex = self.folderPopup.indexOfSelectedItem()
83 if currentIndex >= 2:
84 self.lastIndex = currentIndex
85 NSApp.delegate().addFolder_(self.folderPopup.titleOfSelectedItem())
86 return
87 panel = NSOpenPanel.openPanel()
88 panel.setCanChooseFiles_(False)
89 panel.setCanChooseDirectories_(True)
90 panel.setAllowsMultipleSelection_(False)
91 panel.beginSheetForDirectory_file_types_modalForWindow_modalDelegate_didEndSelector_contextInfo_(
92 None, None, [], self.window, self, self.selectFolderEnd_returnCode_contextInfo_, None)
93
94 @objc.signature("v@:@ii")
95 def selectFolderEnd_returnCode_contextInfo_(self, panel, ret, _):
96 if ret == NSOKButton:
97 assert len(panel.filenames()) == 1
98 folder = panel.filenames()[0]
99 NSApp.delegate().addFolder_(folder)
100 self.folderPopup.insertItemWithTitle_atIndex_(folder, 2)
101 self.folderPopup.selectItemAtIndex_(2)
102 else:
103 self.folderPopup.selectItemAtIndex_(self.lastIndex)
104
105
106 class NotiPodController(NSObject):
107 window = objc.IBOutlet()
108 playlistModel = objc.IBOutlet()
109 folderModel = objc.IBOutlet()
110 loadingSheet = objc.IBOutlet()
111 loadingLabel = objc.IBOutlet()
112
113 def awakeFromNib(self):
114 self.runningGenerator = False
115
116 # Delegate methods
117 def applicationWillFinishLaunching_(self, _):
118 pass
119
120 def applicationDidFinishLaunching_(self, _):
121 self.library = libnotipod.ITunesLibrary.alloc().init()
122 def finish():
123 self.playlistModel.setPlaylists(self.library.get_playlists())
124 self.runGenerator(lambda: self.library.load_(None), finish)
125
126 def applicationWillTerminate_(self, _):
127 self.prefs().synchronize()
128
129 def applicationShouldTerminateAfterLastWindowClosed_(self, _):
130 return True
131
132
133 # Utility methods
134 def runGenerator(self, func, finish):
135 assert not self.runningGenerator
136 self.runningGenerator = True
137 NSApp.beginSheet_modalForWindow_modalDelegate_didEndSelector_contextInfo_(self.loadingSheet, self.window, None, None, None)
138 arg = (func(), finish)
139 self.performSelectorInBackground_withObject_(self.runGeneratorThread, arg)
140
141 def runGeneratorThread(self, (gen, finish)):
142 pool = NSAutoreleasePool.alloc().init()
143 for msg in gen:
144 if not self.runningGenerator:
145 break
146 self.loadingLabel.performSelectorOnMainThread_withObject_waitUntilDone_(
147 self.loadingLabel.setStringValue_, msg, True)
148 self.performSelectorOnMainThread_withObject_waitUntilDone_(
149 self.stopGenerator, finish, True)
150 self.runningGenerator = False
151 del pool
152
153 def stopGenerator(self, finish):
154 self.runningGenerator = False
155 NSApp.endSheet_(self.loadingSheet)
156 self.loadingSheet.orderOut_(self)
157 finish()
158
159 @objc.IBAction
160 def doCancel_(self, sender):
161 self.runningGenerator = False
162
163 @objc.IBAction
164 def doSync_(self, sender):
165 folder = self.folders()[0]
166 playlists = [self.library.get_playlist_pid(pid) for pid in self.playlists()]
167
168 all_tracks = []
169 for playlist in playlists:
170 all_tracks.extend(playlist.tracks)
171 libnotipod.export_m3u(dry_run=False, dest=folder, path_prefix="",
172 playlist_name=playlist.name, files=playlist.tracks)
173
174 def finish():
175 NSRunAlertPanel("Complete!", "Synchronisation is complete", "Ok", None, None)
176 self.runGenerator(
177 lambda:
178 libnotipod.sync(
179 dry_run=False,
180 source=self.library.folder,
181 dest=folder,
182 files_to_copy=all_tracks
183 )
184 ,
185 finish
186 )
187
188
189 # Public accessors
190
191 def prefs(self):
192 return NSUserDefaults.standardUserDefaults()
193
194 def _getArray(self, key):
195 res = self.prefs().stringArrayForKey_(key)
196 return list(res) if res else []
197
198 def _saveArray(self, key, array):
199 self.prefs().setObject_forKey_(array, key)
200
201 def playlists(self):
202 return self._getArray("playlists")
203
204 def folders(self):
205 return self._getArray("folders")
206
207 def addFolder_(self, folder):
208 folders = self.folders()
209 while folder in folders:
210 folders.remove(folder)
211 folders.insert(0, folder)
212 folders = folders[:10]
213 self._saveArray("folders", folders)
214
215 def setPlaylist_selected_(self, playlist, selected):
216 playlists = self.playlists()
217 if selected:
218 playlists.append(playlist)
219 else:
220 playlists.remove(playlist)
221 playlists = list(set(playlists))
222 self._saveArray("playlists", list(set(playlists)))
223
224
225 def main():
226 ### logging.basicConfig(format="%(levelname)s: %(message)s")
227 ### logging.getLogger().setLevel(logging.DEBUG)
228 AppHelper.runEventLoop()
229
230 if __name__ == "__main__":
231 main()
232