]> code.delx.au - notipod/blob - notipod_gui.py
Reordered tags
[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 if not col:
52 return
53 col = col.identifier()
54
55 if col == "selected":
56 selected = NSApp.delegate().playlists()
57 return playlist.pid in selected
58 if col == "icon":
59 return NSImage.imageNamed_("playlist-" + playlist.ptype)
60 if col == "playlist":
61 return playlist.name
62
63 def outlineView_setObjectValue_forTableColumn_byItem_(self, _, v, col, playlist):
64 if not col:
65 return
66 col = col.identifier()
67
68
69 if col != "selected":
70 return
71 NSApp.delegate().setPlaylist_selected_(playlist.pid, v)
72
73
74 class FolderModel(NSObject):
75 window = objc.IBOutlet()
76 folderPopup = objc.IBOutlet()
77
78 def awakeFromNib(self):
79 folders = NSApp.delegate().folders()
80 self.folderPopup.addItemsWithTitles_(folders)
81 if len(folders) > 0:
82 self.folderPopup.selectItemAtIndex_(2)
83 self.lastIndex = 2
84 else:
85 self.lastIndex = 0
86
87 @objc.IBAction
88 def doSelectFolder_(self, sender):
89 currentIndex = self.folderPopup.indexOfSelectedItem()
90 if currentIndex >= 2:
91 self.lastIndex = currentIndex
92 NSApp.delegate().addFolder_(self.folderPopup.titleOfSelectedItem())
93 return
94 panel = NSOpenPanel.openPanel()
95 panel.setCanChooseFiles_(False)
96 panel.setCanChooseDirectories_(True)
97 panel.setAllowsMultipleSelection_(False)
98 panel.beginSheetForDirectory_file_types_modalForWindow_modalDelegate_didEndSelector_contextInfo_(
99 None, None, [], self.window, self, self.selectFolderEnd_returnCode_contextInfo_, None)
100
101 @objc.signature("v@:@ii")
102 def selectFolderEnd_returnCode_contextInfo_(self, panel, ret, _):
103 if ret == NSOKButton:
104 assert len(panel.filenames()) == 1
105 folder = panel.filenames()[0]
106 NSApp.delegate().addFolder_(folder)
107 self.folderPopup.insertItemWithTitle_atIndex_(folder, 2)
108 self.folderPopup.selectItemAtIndex_(2)
109 else:
110 self.folderPopup.selectItemAtIndex_(self.lastIndex)
111
112
113 class NotiPodController(NSObject):
114 window = objc.IBOutlet()
115 playlistModel = objc.IBOutlet()
116 folderModel = objc.IBOutlet()
117 loadingSheet = objc.IBOutlet()
118 loadingLabel = objc.IBOutlet()
119 loadingIndicator = objc.IBOutlet()
120
121 def awakeFromNib(self):
122 self.runningGenerator = False
123
124 # Delegate methods
125 def applicationWillFinishLaunching_(self, _):
126 pass
127
128 def applicationDidFinishLaunching_(self, _):
129 self.library = libnotipod.ITunesLibrary.alloc().init()
130 def finish():
131 self.playlistModel.setPlaylists(self.library.get_playlists())
132 self.runGenerator(lambda: self.library.load_(None), finish)
133
134 def applicationWillTerminate_(self, _):
135 self.prefs().synchronize()
136
137 def applicationShouldTerminateAfterLastWindowClosed_(self, _):
138 return True
139
140
141 # Utility methods
142 def runGenerator(self, func, finish):
143 assert not self.runningGenerator
144 self.runningGenerator = True
145 self.loadingIndicator.startAnimation_(self)
146 NSApp.beginSheet_modalForWindow_modalDelegate_didEndSelector_contextInfo_(self.loadingSheet, self.window, None, None, None)
147 arg = (func(), finish)
148 self.performSelectorInBackground_withObject_(self.runGeneratorThread, arg)
149
150 def runGeneratorThread(self, (gen, finish)):
151 pool = NSAutoreleasePool.alloc().init()
152 for msg in gen:
153 if not self.runningGenerator:
154 break
155 self.loadingLabel.performSelectorOnMainThread_withObject_waitUntilDone_(
156 self.loadingLabel.setStringValue_, msg, True)
157 self.performSelectorOnMainThread_withObject_waitUntilDone_(
158 self.stopGenerator, finish, True)
159 self.runningGenerator = False
160 del pool
161
162 def stopGenerator(self, finish):
163 self.runningGenerator = False
164 NSApp.endSheet_(self.loadingSheet)
165 self.loadingSheet.orderOut_(self)
166 self.loadingIndicator.stopAnimation_(self)
167 finish()
168
169 @objc.IBAction
170 def doCancel_(self, sender):
171 self.runningGenerator = False
172
173 @objc.IBAction
174 def doSync_(self, sender):
175 folder = self.folders()[0]
176 playlists = [self.library.get_playlist_pid(pid) for pid in self.playlists()]
177
178 all_tracks = []
179 for playlist in playlists:
180 all_tracks.extend(playlist.tracks)
181 libnotipod.export_m3u(dry_run=False, dest=folder, path_prefix="",
182 playlist_name=playlist.name, files=playlist.tracks)
183
184 def finish():
185 NSRunAlertPanel("Complete!", "Synchronisation is complete", "Ok", None, None)
186 self.runGenerator(
187 lambda:
188 libnotipod.sync(
189 dry_run=False,
190 source=self.library.folder,
191 dest=folder,
192 files_to_copy=all_tracks
193 )
194 ,
195 finish
196 )
197
198
199 # Public accessors
200
201 def prefs(self):
202 return NSUserDefaults.standardUserDefaults()
203
204 def _getArray(self, key):
205 res = self.prefs().stringArrayForKey_(key)
206 return list(res) if res else []
207
208 def _saveArray(self, key, array):
209 self.prefs().setObject_forKey_(array, key)
210
211 def playlists(self):
212 return self._getArray("playlists")
213
214 def folders(self):
215 return self._getArray("folders")
216
217 def addFolder_(self, folder):
218 folders = self.folders()
219 while folder in folders:
220 folders.remove(folder)
221 folders.insert(0, folder)
222 folders = folders[:10]
223 self._saveArray("folders", folders)
224
225 def setPlaylist_selected_(self, playlist, selected):
226 playlists = self.playlists()
227 if selected:
228 playlists.append(playlist)
229 else:
230 playlists.remove(playlist)
231 playlists = list(set(playlists))
232 self._saveArray("playlists", list(set(playlists)))
233
234
235 def main():
236 ### logging.basicConfig(format="%(levelname)s: %(message)s")
237 ### logging.getLogger().setLevel(logging.DEBUG)
238 AppHelper.runEventLoop()
239
240 if __name__ == "__main__":
241 main()
242