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