Fix bug #5785
[mevemon] / package / src / ui / fremantle / 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 = hildon.AppMenu()
36
37         for command in self.menu_items:
38             # Create menu entries
39             button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
40             button.set_label(command)
41
42             if command == "About":
43                 button.connect("clicked", self.about_clicked)
44             elif command == "Settings":
45                 button.connect("clicked", self.settings_clicked, window)
46             elif command == "Refresh":
47                 button.connect("clicked", self.refresh_clicked)
48             else:
49                 assert False, command
50
51             # Add entry to the view menu
52             menu.append(button)
53
54         menu.show_all()
55
56         return menu
57
58     def settings_clicked(self, button, window):
59
60         RESPONSE_NEW, RESPONSE_EDIT, RESPONSE_DELETE = range(3)
61
62         dialog = gtk.Dialog()
63         dialog.set_transient_for(window)
64         dialog.set_title("Settings")
65
66         pannable_area = hildon.PannableArea()
67
68         dialog_vbox = dialog.vbox
69
70         vbox = gtk.VBox(False, 1)
71
72         self.accounts_model = models.AccountsModel(self.controller)
73         
74         accounts_treeview = hildon.GtkTreeView(gtk.HILDON_UI_MODE_NORMAL)
75         accounts_treeview.set_model(self.accounts_model)
76         accounts_treeview.set_headers_visible(True)
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         
87         pannable_area.add_with_viewport(vbox)
88         
89         dialog_vbox.pack_start(pannable_area, True, True, 1)
90       
91       
92         dialog.show_all()
93
94         result = dialog.run()
95
96         while(result != gtk.RESPONSE_DELETE_EVENT):
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 = hildon.Entry(gtk.HILDON_SIZE_FINGER_HEIGHT)
165         uidEntry.set_placeholder("User ID")
166         uidEntry.set_property('is_focus', False)
167         
168         vbox.add(uidEntry)
169
170         apiLabel = gtk.Label("API key:")
171         apiLabel.set_justify(gtk.JUSTIFY_LEFT)
172         vbox.add(apiLabel)
173         
174         apiEntry = hildon.Entry(gtk.HILDON_SIZE_FINGER_HEIGHT)
175         apiEntry.set_placeholder("API Key")
176         apiEntry.set_property('is_focus', False)
177
178         vbox.add(apiEntry)
179        
180         ok_button = dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
181
182         dialog.show_all()
183         result = dialog.run()
184         
185         valid_credentials = False
186
187         while not valid_credentials:
188             if result == gtk.RESPONSE_OK:
189                 uid = uidEntry.get_text()
190                 api_key = apiEntry.get_text()
191             
192                 try:
193                     validation.uid(uid)
194                     validation.api_key(api_key)
195                 except validation.ValidationError, e:
196                     self.report_error(e.message)
197                     result = dialog.run()
198                 else:
199                     valid_credentials = True
200                     self.controller.add_account(uid, api_key)
201                     self.accounts_model.get_accounts()
202             else:
203                 break
204
205         dialog.destroy()
206
207
208     def report_error(self, error):
209         hildon.hildon_banner_show_information(self.win, '', error)
210     
211     def about_clicked(self, button):
212         dialog = gtk.AboutDialog()
213         dialog.set_website(self.controller.about_website)
214         dialog.set_website_label(self.controller.about_website)
215         dialog.set_name(self.controller.about_name)
216         dialog.set_authors(self.controller.about_authors)
217         dialog.set_comments(self.controller.about_text)
218         dialog.set_version(self.controller.app_version)
219         dialog.run()
220         dialog.destroy()
221
222     def add_label(self, text, box, markup=True, align="left", padding=1):
223         label = gtk.Label(text)
224         if markup:
225             label.set_use_markup(True)
226         if align == "left":
227             label.set_alignment(0, 0.5)
228
229         box.pack_start(label, False, False, padding)
230
231         return label
232
233 class mEveMonUI(BaseUI):
234
235     def __init__(self, controller):
236         self.controller = controller
237         gtk.set_application_name("mEveMon")
238
239         # create the main window
240         self.win = hildon.StackableWindow()
241         self.win.connect("destroy", self.controller.quit)
242         self.win.show_all()
243         hildon.hildon_gtk_window_set_progress_indicator(self.win, 1)
244
245         # Create menu
246         menu = self.create_menu(self.win)
247         # Attach menu to the window
248         self.win.set_app_menu(menu)
249
250         pannable_area = hildon.PannableArea()
251
252         character_win = CharacterSheetUI(self.controller)
253
254         # gtk.HILDON_UI_MODE_NORMAL -> not selection in the treeview
255         # gtk.HILDON_UI_MODE_EDIT -> selection in the treeview
256         treeview = hildon.GtkTreeView(gtk.HILDON_UI_MODE_NORMAL)
257         treeview.connect('row-activated', character_win.build_window)
258
259         self.char_model = models.CharacterListModel(self.controller)
260         treeview.set_model(self.char_model)
261         self.add_columns_to_treeview(treeview)
262
263         pannable_area.add(treeview)
264
265         self.win.add(pannable_area);
266         
267         self.win.show_all()
268
269         hildon.hildon_gtk_window_set_progress_indicator(self.win, 0)
270
271     def add_columns_to_treeview(self, treeview):
272         #Column 0 for the treeview
273         renderer = gtk.CellRendererPixbuf()
274         column = gtk.TreeViewColumn()
275         column.pack_start(renderer, True)
276         column.add_attribute(renderer, "pixbuf", 
277                 models.CharacterListModel.C_PORTRAIT)
278         treeview.append_column(column)
279
280         #Column 1 for the treeview
281         renderer = gtk.CellRendererText()
282         column = gtk.TreeViewColumn('Character Name', renderer, 
283                 text=models.CharacterListModel.C_NAME)
284         column.set_property("expand", True)
285         treeview.append_column(column)
286  
287     def refresh_clicked(self, button):
288         hildon.hildon_gtk_window_set_progress_indicator(self.win, 1)
289         self.char_model.get_characters()
290         hildon.hildon_gtk_window_set_progress_indicator(self.win, 0)
291     
292
293 class CharacterSheetUI(BaseUI):
294     UPDATE_INTERVAL = 1
295
296     def __init__(self, controller):
297         self.controller = controller
298         self.sheet = None
299         self.char_id = None
300         self.skills_model = None
301
302
303     def build_window(self, treeview, path, view_column):
304         # TODO: this is a really long and ugly function, split it up somehow
305
306         self.win = hildon.StackableWindow()
307         hildon.hildon_gtk_window_set_progress_indicator(self.win, 1)
308         self.win.show_all() 
309
310         # Create menu
311         # NOTE: we probably want a window-specific menu for this page, but the
312         # main appmenu works for now
313         menu = self.create_menu(self.win)
314         # Attach menu to the window
315         self.win.set_app_menu(menu)
316
317         pannable_area = hildon.PannableArea()
318
319         model = treeview.get_model()
320         miter = model.get_iter(path)
321         
322         # column 0 is the portrait, column 1 is name
323         char_name = model.get_value(miter, 1)
324         self.uid = model.get_value(miter, 2)
325         self.char_id = self.controller.char_name2id(char_name)
326
327         self.sheet = self.controller.get_char_sheet(self.uid, self.char_id)
328
329         self.win.set_title(char_name)
330
331
332         hbox = gtk.HBox(False, 0)
333         info_vbox = gtk.VBox(False, 0)
334
335         portrait = gtk.Image()
336         portrait.set_from_file(self.controller.get_portrait(char_name, 256))
337         portrait.show()
338
339         hbox.pack_start(portrait, False, False, 10)
340         hbox.pack_start(info_vbox, False, False, 5)
341
342         vbox = gtk.VBox(False, 0)
343         pannable_area.add_with_viewport(vbox)
344
345         vbox.pack_start(hbox, False, False, 0)
346
347         self.fill_info(info_vbox)
348         self.fill_stats(info_vbox)
349
350         separator = gtk.HSeparator()
351         vbox.pack_start(separator, False, False, 5)
352         separator.show()
353
354         
355         self.add_label("<big>Skill in Training:</big>", vbox, align="normal")
356
357         self.display_skill_in_training(vbox)
358
359         separator = gtk.HSeparator()
360         vbox.pack_start(separator, False, False, 0)
361         separator.show()
362         
363         self.add_label("<big>Skills:</big>", vbox, align="normal")
364
365         skills_treeview = hildon.GtkTreeView(gtk.HILDON_UI_MODE_NORMAL)
366         self.skills_model = models.CharacterSkillsModel(self.controller, self.char_id)
367         skills_treeview.set_model(self.skills_model)
368         self.add_columns_to_skills_view(skills_treeview)
369         vbox.pack_start(skills_treeview, False, False, 1)
370
371         self.win.add(pannable_area)
372         self.win.show_all()
373
374         hildon.hildon_gtk_window_set_progress_indicator(self.win, 0)
375         
376         # if we start the timer too early, get_is_topmost() returns False
377         glib.timeout_add_seconds(self.UPDATE_INTERVAL, self.update_live_sp)
378
379     def display_skill_in_training(self, vbox):
380         skill = self.controller.get_skill_in_training(self.uid, self.char_id)
381         
382         if skill.skillInTraining:
383
384             skilltree = self.controller.get_skill_tree()
385             
386             # I'm assuming that we will always find a skill with the skill ID
387             for group in skilltree.skillGroups:
388                 found_skill = group.skills.Get(skill.trainingTypeID, False)
389                 if found_skill:
390                     skill_name = found_skill.typeName
391                     break
392                 
393             self.add_label("%s <small>(Level %d)</small>" % (skill_name, skill.trainingToLevel),
394                     vbox, align="normal")
395             self.add_label("<small>start time: %s\t\tend time: %s</small>" 
396                     %(time.ctime(skill.trainingStartTime),
397                 time.ctime(skill.trainingEndTime)), vbox, align="normal")
398
399             progressbar = gtk.ProgressBar()
400             fraction_completed = (time.time() - skill.trainingStartTime) / \
401                     (skill.trainingEndTime - skill.trainingStartTime)
402
403             progressbar.set_fraction(fraction_completed)
404             align = gtk.Alignment(0.5, 0.5, 0.5, 0)
405             vbox.pack_start(align, False, False, 5)
406             align.show()
407             align.add(progressbar)
408             progressbar.show()
409         else:
410             self.add_label("<small>No skills are currently being trained</small>",
411                     vbox, align="normal")
412
413
414
415     def fill_info(self, box):
416         self.add_label("<big><big>%s</big></big>" % self.sheet.name, box)
417         self.add_label("<small>%s %s %s</small>" % (self.sheet.gender, 
418             self.sheet.race, self.sheet.bloodLine), box)
419         self.add_label("", box, markup=False)
420         self.add_label("<small><b>Corp:</b> %s</small>" % self.sheet.corporationName, box)
421         self.add_label("<small><b>Balance:</b> %s ISK</small>" % 
422                 util.comma(self.sheet.balance), box)
423
424         self.live_sp_val = self.controller.get_sp(self.uid, self.char_id)
425         self.live_sp = self.add_label("<small><b>Total SP:</b> %s</small>" %
426                 util.comma(int(self.live_sp_val)), box)
427         
428         self.spps = self.controller.get_spps(self.uid, self.char_id)[0]
429
430
431     def fill_stats(self, box):
432
433         atr = self.sheet.attributes
434
435         self.add_label("<small><b>I: </b>%d  <b>M: </b>%d  <b>C: </b>%d  " \
436                 "<b>P: </b>%d  <b>W: </b>%d</small>" % (atr.intelligence,
437                     atr.memory, atr.charisma, atr.perception, atr.willpower), box)
438
439
440     def add_columns_to_skills_view(self, treeview):
441         #Column 0 for the treeview
442         renderer = gtk.CellRendererText()
443         column = gtk.TreeViewColumn('Skill Name', renderer, 
444                 markup=models.CharacterSkillsModel.C_NAME)
445         column.set_property("expand", True)
446         treeview.append_column(column)
447         
448         #Column 1 for the treeview
449         column = gtk.TreeViewColumn('Rank', renderer, 
450                 markup=models.CharacterSkillsModel.C_RANK)
451         column.set_property("expand", True)
452         treeview.append_column(column)
453
454         #Column 2
455         column = gtk.TreeViewColumn('Points', renderer,
456                 markup=models.CharacterSkillsModel.C_SKILLPOINTS)
457         column.set_property("expand", True)
458         treeview.append_column(column)
459
460         #Column 3
461         column = gtk.TreeViewColumn('Level', renderer, 
462                 markup=models.CharacterSkillsModel.C_LEVEL)
463         column.set_property("expand", True)
464         treeview.append_column(column)
465
466
467     def refresh_clicked(self, button):
468         hildon.hildon_gtk_window_set_progress_indicator(self.win, 1)
469         self.skills_model.get_skills()
470         hildon.hildon_gtk_window_set_progress_indicator(self.win, 0)
471
472
473     def update_live_sp(self):
474         # we don't want to keep the timer running in the background
475         # when this callback returns False, the timer destorys itself
476         
477         # TODO: figure out why this doesn't work on the real device
478         #
479         #if not self.win.get_is_topmost():
480         #    return False
481         
482         self.live_sp_val = self.live_sp_val + self.spps * self.UPDATE_INTERVAL
483         self.live_sp.set_label("<small><b>Total SP:</b> %s</small>" %
484                                 util.comma(int(self.live_sp_val)))
485
486         return True
487
488
489 if __name__ == "__main__":
490     main()
491