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