ac83891d720c661e2e0c246aaecec1a5ca334a6f
[mevemon] / package / src / ui / diablo / gui.py
1 #
2 # mEveMon - A character monitor for EVE Online
3 # Copyright (c) 2010  Ryan and Danny Campbell, and the mEveMon Team
4 #
5 # mEveMon is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # mEveMon is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 #
18
19 import sys, time
20
21 import gtk
22 import hildon
23 import gobject
24
25 from ui import models
26 import validation
27 import util
28 import constants
29
30 class BaseUI():
31     menu_items = ("Settings", "About", "Refresh")
32
33     def create_menu(self, window):
34         menu = gtk.Menu()
35
36         for command in self.menu_items:
37             # Create menu entries
38             button = gtk.MenuItem(command)
39
40             if command == "About":
41                 button.connect("activate", self.about_clicked)
42             elif command == "Settings":
43                 button.connect("activate", self.settings_clicked, window)
44             elif command == "Refresh":
45                 button.connect("activate", self.refresh_clicked, window)
46             else:
47                 assert False, command
48
49             # Add entry to the view menu
50             menu.append(button)
51
52         menu.show_all()
53
54         return menu
55
56     def settings_clicked(self, button, window):
57
58         RESPONSE_NEW, RESPONSE_EDIT, RESPONSE_DELETE = range(3)
59
60         dialog = gtk.Dialog()
61         dialog.set_transient_for(window)
62         dialog.set_title("Settings")
63
64         vbox = dialog.vbox
65
66         acctsLabel = gtk.Label("Accounts:")
67         acctsLabel.set_justify(gtk.JUSTIFY_LEFT)
68
69         vbox.pack_start(acctsLabel, False, False, 1)
70
71         self.accounts_model = models.AccountsModel(self.controller)
72
73         accounts_treeview = gtk.TreeView(model = self.accounts_model)
74         self.add_columns_to_accounts(accounts_treeview)
75         vbox.pack_start(accounts_treeview, False, False, 1)
76         
77         clear_button = gtk.Button("Clear Cache")
78         clear_button.connect("clicked", self.on_clear_cache_clicked)
79         vbox.pack_start(clear_button, False, False, 1)
80
81         # all stock responses are negative, so we can use any positive value
82         new_button = dialog.add_button("New", RESPONSE_NEW)
83         #TODO: get edit button working
84         #edit_button = dialog.add_button("Edit", RESPONSE_EDIT)
85         delete_button = dialog.add_button("Delete", RESPONSE_DELETE)
86         ok_button = dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
87         cancel_button = dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
88
89         #TODO: for some reason the scrollbar shows up in the middle of the
90         # dialog. Why?
91         #scrollbar = gtk.VScrollbar()
92         
93         dialog.show_all()
94
95         result = dialog.run()
96
97         while(result != gtk.RESPONSE_CANCEL):
98             if result == RESPONSE_NEW:
99                 self.new_account_clicked(window)
100             #elif result == RESPONSE_EDIT:
101             #    # get the selected treeview item and pop up the account_box
102             #    self.edit_account(accounts_treeview)
103             elif result == RESPONSE_DELETE:
104                 # get the selected treeview item, and delete the gconf keys
105                 self.delete_account(accounts_treeview)
106             elif result == gtk.RESPONSE_OK:
107                 self.char_model.get_characters()
108                 break
109         
110             result = dialog.run()
111
112         dialog.destroy()
113
114     def on_clear_cache_clicked(self, button):
115         self.controller.clear_cache()
116
117
118     def get_selected_item(self, treeview, column):
119         selection = treeview.get_selection()
120         model, miter = selection.get_selected()
121
122         value = model.get_value(miter, column)
123
124         return value
125
126     def edit_account(self, treeview):
127         uid = self.get_selected_item(treeview, 0)
128         # pop up the account dialog
129
130         self.accounts_model.get_accounts()
131
132     def delete_account(self, treeview):
133         uid = self.get_selected_item(treeview, 0)
134         self.controller.settings.remove_account(uid)
135         # refresh model
136         self.accounts_model.get_accounts()
137
138
139     def add_columns_to_accounts(self, treeview):
140         #Column 0 for the treeview
141         renderer = gtk.CellRendererText()
142         column = gtk.TreeViewColumn('User ID', renderer, 
143                 text=models.AccountsModel.C_UID)
144         column.set_property("expand", True)
145         treeview.append_column(column)
146
147         #Column 2 (characters) for the treeview
148         column = gtk.TreeViewColumn('Characters', renderer, 
149                 markup=models.AccountsModel.C_CHARS)
150         column.set_property("expand", True)
151         treeview.append_column(column)
152
153
154     def new_account_clicked(self, window, widget):
155         dialog = gtk.Dialog()
156     
157         #get the vbox to pack all the settings into
158         vbox = dialog.vbox
159     
160         dialog.set_transient_for(window)
161         dialog.set_title("New Account")
162
163         uidLabel = gtk.Label("User ID:")
164         uidLabel.set_justify(gtk.JUSTIFY_LEFT)
165         vbox.add(uidLabel)
166         
167         uidEntry = gtk.Entry()
168         uidEntry.set_property('is_focus', False)
169         
170         vbox.add(uidEntry)
171
172         apiLabel = gtk.Label("API key:")
173         apiLabel.set_justify(gtk.JUSTIFY_LEFT)
174         vbox.add(apiLabel)
175         
176         apiEntry = gtk.Entry()
177         apiEntry.set_property('is_focus', False)
178
179         vbox.add(apiEntry)
180        
181         ok_button = dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
182         cancel_button = dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
183
184         dialog.show_all()
185         result = dialog.run()
186         
187         valid_credentials = False
188
189         while not valid_credentials:
190             if result == gtk.RESPONSE_OK:
191                 uid = uidEntry.get_text()
192                 api_key = apiEntry.get_text()
193             
194                 try:
195                     validation.validate_uid(uid)
196                     validation.validate_api_key(api_key)
197                 except validation.ValidationError, e:
198                     self.report_error(e.message)
199                     result = dialog.run()
200                 else:
201                     valid_credentials = True
202                     self.controller.settings.add_account(uid, api_key)
203                     self.accounts_model.get_accounts()
204             else:
205                 break
206
207         dialog.destroy()
208
209
210     def report_error(self, error):
211         hildon.hildon_banner_show_information(self.win, '', error)
212     
213     def about_clicked(self, button):
214         dialog = gtk.AboutDialog()
215         dialog.set_website(constants.ABOUT_WEBSITE)
216         dialog.set_website_label(constants.ABOUT_WEBSITE)
217         dialog.set_name(constants.ABOUT_NAME)
218         dialog.set_authors(constants.ABOUT_AUTHORS)
219         dialog.set_comments(constants.ABOUT_TEXT)
220         dialog.set_version(constants.APP_VERSION)
221         dialog.run()
222         dialog.destroy()
223
224     def add_label(self, text, box, markup=True, align="left", padding=1):
225         label = gtk.Label(text)
226         if markup:
227             label.set_use_markup(True)
228         if align == "left":
229             label.set_alignment(0, 0.5)
230
231         box.pack_start(label, False, False, padding)
232
233         return label
234
235 class mEveMonUI(BaseUI):
236
237     def __init__(self, controller):
238         self.controller = controller
239         gtk.set_application_name("mEveMon")
240
241     def run(self):
242         # create the main window
243         self.win = hildon.Window()
244         self.win.connect("destroy", self.controller.quit)
245         self.win.show_all()
246
247         wait_anim = hildon.hildon_banner_show_animation(self.win, None, "Loading overview...")        
248         # it would seem that on diablo we have to wait for gtk to
249         # get its ass in gear...
250         while gtk.events_pending():
251             gtk.main_iteration()
252
253         # Create menu
254         menu = self.create_menu(self.win)
255
256         # Attach menu to the window
257         self.win.set_menu(menu)
258
259         # create the treeview --danny
260         self.char_model = models.CharacterListModel(self.controller)
261         treeview = gtk.TreeView(model = self.char_model)
262         treeview.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_HORIZONTAL)
263         treeview.connect('row-activated', self.do_charactersheet)
264         treeview.set_model(self.char_model)
265         self.add_columns_to_treeview(treeview)
266
267         # add the treeview with scrollbar --danny
268         self.win.add_with_scrollbar(treeview)
269         self.win.show_all()
270
271         wait_anim.destroy()
272
273     def add_columns_to_treeview(self, treeview):
274         #Column 0 for the treeview
275         renderer = gtk.CellRendererPixbuf()
276         column = gtk.TreeViewColumn()
277         column.pack_start(renderer, True)
278         column.add_attribute(renderer, "pixbuf", 
279                 models.CharacterListModel.C_PORTRAIT)
280         treeview.append_column(column)
281
282         #Column 1 for the treeview
283         renderer = gtk.CellRendererText()
284         column = gtk.TreeViewColumn('Character Name', renderer, 
285                 text=models.CharacterListModel.C_NAME)
286         column.set_property("expand", True)
287         treeview.append_column(column)
288  
289     def refresh_clicked(self, button, window):
290         wait_anim = hildon.hildon_banner_show_animation(self.win, None, "Refreshing view...")        
291         # let gtk catch up...
292         while gtk.events_pending():
293             gtk.main_iteration()
294         self.char_model.get_characters()
295         wait_anim.destroy()
296
297     def do_charactersheet(self, treeview, path, view_column):
298
299         model = treeview.get_model()
300         miter = model.get_iter(path)
301         
302         # column 0 is the portrait, column 1 is name
303         char_name = model.get_value(miter, 1)
304         uid = model.get_value(miter, 2)
305         
306         if uid:
307             CharacterSheetUI(self.controller, char_name, uid)
308         else:
309             pass
310
311 class CharacterSheetUI(BaseUI):
312     #time between live sp updates (in milliseconds)
313     UPDATE_INTERVAL = 1000
314
315     def __init__(self, controller, char_name, uid):
316         self.controller = controller
317         self.char_name = char_name
318         self.uid = uid
319         self.sheet = None
320         self.char_id = None
321         self.skills_model = None
322
323         self.build_window()
324
325     def build_window(self):
326         # TODO: this is a really long and ugly function, split it up somehow
327
328         self.win = hildon.Window()
329
330         self.win.show_all() 
331
332         wait_anim = hildon.hildon_banner_show_animation(self.win, None, "Loading character sheet...")        
333         # let gtk catch up...
334         while gtk.events_pending():
335             gtk.main_iteration()
336
337         # Create menu
338         # NOTE: we probably want a window-specific menu for this page, but the
339         # main appmenu works for now
340         menu = self.create_menu(self.win)
341         # Attach menu to the window
342         self.win.set_menu(menu)
343
344         self.char_id = self.controller.char_name2id(self.char_name)
345
346         self.sheet = self.controller.get_char_sheet(self.uid, self.char_id)
347
348         self.win.set_title(self.char_name)
349
350
351         hbox = gtk.HBox(False, 0)
352         info_vbox = gtk.VBox(False, 0)
353
354         portrait = gtk.Image()
355         portrait.set_from_file(self.controller.get_portrait(self.char_name, 256))
356         portrait.show()
357
358         hbox.pack_start(portrait, False, False, 10)
359         hbox.pack_start(info_vbox, False, False, 5)
360
361         vbox = gtk.VBox(False, 0)
362
363         vbox.pack_start(hbox, False, False, 0)
364
365         self.fill_info(info_vbox)
366         self.fill_stats(info_vbox)
367
368         separator = gtk.HSeparator()
369         vbox.pack_start(separator, False, False, 5)
370         separator.show()
371
372         
373         self.add_label("<big>Skill in Training:</big>", vbox, align="normal")
374
375         self.display_skill_in_training(vbox)
376
377         separator = gtk.HSeparator()
378         vbox.pack_start(separator, False, False, 0)
379         separator.show()
380         
381         self.add_label("<big>Skills:</big>", vbox, align="normal")
382
383
384         self.skills_model = models.CharacterSkillsModel(self.controller, self.char_id)
385         skills_treeview = gtk.TreeView(model=self.skills_model)
386         self.add_columns_to_skills_view(skills_treeview)
387
388         vbox.pack_start(skills_treeview, False, False, 0)
389
390         self.win.add_with_scrollbar(vbox)
391         self.win.show_all()
392
393         wait_anim.destroy()
394         
395         # diablo doesnt have a glib module, but gobject module seems to have
396         # the same functions...
397         self.timer = gobject.timeout_add(self.UPDATE_INTERVAL, self.update_live_sp)
398         self.win.connect("destroy", self.back)
399
400     def back(self, widget):
401         gobject.source_remove(self.timer)
402         gtk.Window.destroy(self.win)
403
404     def display_skill_in_training(self, vbox):
405         skill = self.controller.get_skill_in_training(self.uid, self.char_id)
406         
407         if skill.skillInTraining:
408
409             skilltree = self.controller.get_skill_tree()
410             
411             # I'm assuming that we will always find a skill with the skill ID
412             for group in skilltree.skillGroups:
413                 found_skill = group.skills.Get(skill.trainingTypeID, False)
414                 if found_skill:
415                     skill_name = found_skill.typeName
416                     break
417                 
418             self.add_label("%s <small>(Level %d)</small>" % (skill_name, skill.trainingToLevel),
419                     vbox, align="normal")
420             self.add_label("<small>start time: %s\t\tend time: %s</small>" 
421                     %(time.ctime(skill.trainingStartTime),
422                 time.ctime(skill.trainingEndTime)), vbox, align="normal")
423
424             progressbar = gtk.ProgressBar()
425             fraction_completed = (time.time() - skill.trainingStartTime) / \
426                     (skill.trainingEndTime - skill.trainingStartTime)
427
428             progressbar.set_fraction(fraction_completed)
429             align = gtk.Alignment(0.5, 0.5, 0.5, 0)
430             vbox.pack_start(align, False, False, 5)
431             align.show()
432             align.add(progressbar)
433             progressbar.show()
434         else:
435             self.add_label("<small>No skills are currently being trained</small>",
436                     vbox, align="normal")
437
438
439
440     def fill_info(self, box):
441         self.add_label("<big><big>%s</big></big>" % self.sheet.name, box)
442         self.add_label("<small>%s %s %s</small>" % (self.sheet.gender, 
443             self.sheet.race, self.sheet.bloodLine), box)
444         self.add_label("", box, markup=False)
445         self.add_label("<small><b>Corp:</b> %s</small>" % self.sheet.corporationName, box)
446         self.add_label("<small><b>Balance:</b> %s ISK</small>" % 
447                 util.comma(self.sheet.balance), box)
448
449         self.live_sp_val = self.controller.get_sp(self.uid, self.char_id)
450         self.live_sp = self.add_label("<small><b>Total SP:</b> %s</small>" %
451                 util.comma(int(self.live_sp_val)), box)
452         
453         self.spps = self.controller.get_spps(self.uid, self.char_id)[0]
454
455
456     def fill_stats(self, box):
457
458         atr = self.sheet.attributes
459
460         self.add_label("<small><b>I: </b>%d  <b>M: </b>%d  <b>C: </b>%d  " \
461                 "<b>P: </b>%d  <b>W: </b>%d</small>" % (atr.intelligence,
462                     atr.memory, atr.charisma, atr.perception, atr.willpower), box)
463
464
465     def add_columns_to_skills_view(self, treeview):
466         #Column 0 for the treeview
467         renderer = gtk.CellRendererText()
468         column = gtk.TreeViewColumn('Skill Name', renderer, 
469                 markup=models.CharacterSkillsModel.C_NAME)
470         column.set_property("expand", True)
471         treeview.append_column(column)
472         
473         #Column 1 for the treeview
474         column = gtk.TreeViewColumn('Rank', renderer, 
475                 markup=models.CharacterSkillsModel.C_RANK)
476         column.set_property("expand", True)
477         treeview.append_column(column)
478
479         #Column 2
480         column = gtk.TreeViewColumn('Points', renderer,
481                 markup=models.CharacterSkillsModel.C_SKILLPOINTS)
482         column.set_property("expand", True)
483         treeview.append_column(column)
484
485         #Column 3
486         column = gtk.TreeViewColumn('Level', renderer, 
487                 markup=models.CharacterSkillsModel.C_LEVEL)
488         column.set_property("expand", True)
489         treeview.append_column(column)
490
491
492     def refresh_clicked(self, button, window):
493         wait_anim = hildon.hildon_banner_show_animation(self.win, None, "Loading overview...")        
494         # let gtk catch up...
495         while gtk.events_pending():
496             gtk.main_iteration()
497         self.skills_model.get_skills()
498         wait_anim.destroy()
499
500     def update_live_sp(self):
501         self.live_sp_val = self.live_sp_val + self.spps * (self.UPDATE_INTERVAL / 1000)
502         self.live_sp.set_label("<small><b>Total SP:</b> %s</small>" %
503                                 util.comma(int(self.live_sp_val)))
504
505         return True
506
507
508 if __name__ == "__main__":
509     main()
510