ceab99ad0a72372118dcf6ccdc11fbfcda8109af
[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):
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 notification start --danny
248
249         # Create menu
250         menu = self.create_menu(self.win)
251
252         # Attach menu to the window
253         self.win.set_menu(menu)
254
255
256         # create the treeview --danny
257         self.char_model = models.CharacterListModel(self.controller)
258         treeview = gtk.TreeView(model = self.char_model)
259         treeview.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_HORIZONTAL)
260         treeview.connect('row-activated', self.do_charactersheet)
261         treeview.set_model(self.char_model)
262         self.add_columns_to_treeview(treeview)
263
264         # add the treeview with scrollbar --danny
265         self.win.add_with_scrollbar(treeview)
266         self.win.show_all()
267
268         # wait notification end --danny
269
270     def add_columns_to_treeview(self, treeview):
271         #Column 0 for the treeview
272         renderer = gtk.CellRendererPixbuf()
273         column = gtk.TreeViewColumn()
274         column.pack_start(renderer, True)
275         column.add_attribute(renderer, "pixbuf", 
276                 models.CharacterListModel.C_PORTRAIT)
277         treeview.append_column(column)
278
279         #Column 1 for the treeview
280         renderer = gtk.CellRendererText()
281         column = gtk.TreeViewColumn('Character Name', renderer, 
282                 text=models.CharacterListModel.C_NAME)
283         column.set_property("expand", True)
284         treeview.append_column(column)
285  
286     def refresh_clicked(self, button):
287         self.char_model.get_characters()
288         # wait notification end --danny
289
290     def do_charactersheet(self, treeview, path, view_column):
291
292         model = treeview.get_model()
293         miter = model.get_iter(path)
294         
295         # column 0 is the portrait, column 1 is name
296         char_name = model.get_value(miter, 1)
297         uid = model.get_value(miter, 2)
298         
299         if uid:
300             CharacterSheetUI(self.controller, char_name, uid)
301         else:
302             pass
303
304 class CharacterSheetUI(BaseUI):
305     #time between live sp updates (in milliseconds)
306     UPDATE_INTERVAL = 1000
307
308     def __init__(self, controller, char_name, uid):
309         self.controller = controller
310         self.char_name = char_name
311         self.uid = uid
312         self.sheet = None
313         self.char_id = None
314         self.skills_model = None
315
316         self.build_window()
317
318     def build_window(self):
319         # TODO: this is a really long and ugly function, split it up somehow
320
321         self.win = hildon.Window()
322
323         self.win.show_all() 
324
325         # wait notification start --danny
326
327         # Create menu
328         # NOTE: we probably want a window-specific menu for this page, but the
329         # main appmenu works for now
330         menu = self.create_menu(self.win)
331         # Attach menu to the window
332         self.win.set_menu(menu)
333
334         self.char_id = self.controller.char_name2id(self.char_name)
335
336         self.sheet = self.controller.get_char_sheet(self.uid, self.char_id)
337
338         self.win.set_title(self.char_name)
339
340
341         hbox = gtk.HBox(False, 0)
342         info_vbox = gtk.VBox(False, 0)
343
344         portrait = gtk.Image()
345         portrait.set_from_file(self.controller.get_portrait(self.char_name, 256))
346         portrait.show()
347
348         hbox.pack_start(portrait, False, False, 10)
349         hbox.pack_start(info_vbox, False, False, 5)
350
351         vbox = gtk.VBox(False, 0)
352
353         vbox.pack_start(hbox, False, False, 0)
354
355         self.fill_info(info_vbox)
356         self.fill_stats(info_vbox)
357
358         separator = gtk.HSeparator()
359         vbox.pack_start(separator, False, False, 5)
360         separator.show()
361
362         
363         self.add_label("<big>Skill in Training:</big>", vbox, align="normal")
364
365         self.display_skill_in_training(vbox)
366
367         separator = gtk.HSeparator()
368         vbox.pack_start(separator, False, False, 0)
369         separator.show()
370         
371         self.add_label("<big>Skills:</big>", vbox, align="normal")
372
373
374         self.skills_model = models.CharacterSkillsModel(self.controller, self.char_id)
375         skills_treeview = gtk.TreeView(model=self.skills_model)
376         self.add_columns_to_skills_view(skills_treeview)
377
378         vbox.pack_start(skills_treeview, False, False, 0)
379
380         self.win.add_with_scrollbar(vbox)
381         self.win.show_all()
382
383         # wait notification end --danny
384         
385         # diablo doesnt have a glib module, but gobject module seems to have
386         # the same functions...
387         self.timer = gobject.timeout_add(self.UPDATE_INTERVAL, self.update_live_sp)
388         self.win.connect("destroy", self.back)
389
390     def back(self, widget):
391         gobject.source_remove(self.timer)
392         gtk.Window.destroy(self.win)
393
394     def display_skill_in_training(self, vbox):
395         skill = self.controller.get_skill_in_training(self.uid, self.char_id)
396         
397         if skill.skillInTraining:
398
399             skilltree = self.controller.get_skill_tree()
400             
401             # I'm assuming that we will always find a skill with the skill ID
402             for group in skilltree.skillGroups:
403                 found_skill = group.skills.Get(skill.trainingTypeID, False)
404                 if found_skill:
405                     skill_name = found_skill.typeName
406                     break
407                 
408             self.add_label("%s <small>(Level %d)</small>" % (skill_name, skill.trainingToLevel),
409                     vbox, align="normal")
410             self.add_label("<small>start time: %s\t\tend time: %s</small>" 
411                     %(time.ctime(skill.trainingStartTime),
412                 time.ctime(skill.trainingEndTime)), vbox, align="normal")
413
414             progressbar = gtk.ProgressBar()
415             fraction_completed = (time.time() - skill.trainingStartTime) / \
416                     (skill.trainingEndTime - skill.trainingStartTime)
417
418             progressbar.set_fraction(fraction_completed)
419             align = gtk.Alignment(0.5, 0.5, 0.5, 0)
420             vbox.pack_start(align, False, False, 5)
421             align.show()
422             align.add(progressbar)
423             progressbar.show()
424         else:
425             self.add_label("<small>No skills are currently being trained</small>",
426                     vbox, align="normal")
427
428
429
430     def fill_info(self, box):
431         self.add_label("<big><big>%s</big></big>" % self.sheet.name, box)
432         self.add_label("<small>%s %s %s</small>" % (self.sheet.gender, 
433             self.sheet.race, self.sheet.bloodLine), box)
434         self.add_label("", box, markup=False)
435         self.add_label("<small><b>Corp:</b> %s</small>" % self.sheet.corporationName, box)
436         self.add_label("<small><b>Balance:</b> %s ISK</small>" % 
437                 util.comma(self.sheet.balance), box)
438
439         self.live_sp_val = self.controller.get_sp(self.uid, self.char_id)
440         self.live_sp = self.add_label("<small><b>Total SP:</b> %s</small>" %
441                 util.comma(int(self.live_sp_val)), box)
442         
443         self.spps = self.controller.get_spps(self.uid, self.char_id)[0]
444
445
446     def fill_stats(self, box):
447
448         atr = self.sheet.attributes
449
450         self.add_label("<small><b>I: </b>%d  <b>M: </b>%d  <b>C: </b>%d  " \
451                 "<b>P: </b>%d  <b>W: </b>%d</small>" % (atr.intelligence,
452                     atr.memory, atr.charisma, atr.perception, atr.willpower), box)
453
454
455     def add_columns_to_skills_view(self, treeview):
456         #Column 0 for the treeview
457         renderer = gtk.CellRendererText()
458         column = gtk.TreeViewColumn('Skill Name', renderer, 
459                 markup=models.CharacterSkillsModel.C_NAME)
460         column.set_property("expand", True)
461         treeview.append_column(column)
462         
463         #Column 1 for the treeview
464         column = gtk.TreeViewColumn('Rank', renderer, 
465                 markup=models.CharacterSkillsModel.C_RANK)
466         column.set_property("expand", True)
467         treeview.append_column(column)
468
469         #Column 2
470         column = gtk.TreeViewColumn('Points', renderer,
471                 markup=models.CharacterSkillsModel.C_SKILLPOINTS)
472         column.set_property("expand", True)
473         treeview.append_column(column)
474
475         #Column 3
476         column = gtk.TreeViewColumn('Level', renderer, 
477                 markup=models.CharacterSkillsModel.C_LEVEL)
478         column.set_property("expand", True)
479         treeview.append_column(column)
480
481
482     def refresh_clicked(self, button):
483         self.skills_model.get_skills()
484         # wait notification end --danny
485
486     def update_live_sp(self):
487         self.live_sp_val = self.live_sp_val + self.spps * (self.UPDATE_INTERVAL / 1000)
488         self.live_sp.set_label("<small><b>Total SP:</b> %s</small>" %
489                                 util.comma(int(self.live_sp_val)))
490
491         return True
492
493
494 if __name__ == "__main__":
495     main()
496