| 1 | #!/usr/bin/env python |
|---|
| 2 | # -*- coding: utf-8 -*- |
|---|
| 3 | import sys, time, os |
|---|
| 4 | import string, threading |
|---|
| 5 | #subfolders are added to the path |
|---|
| 6 | sys.path[1:1]=["storage", "protocols"] |
|---|
| 7 | |
|---|
| 8 | try: |
|---|
| 9 | import pygtk |
|---|
| 10 | pygtk.require("2.0") |
|---|
| 11 | except: |
|---|
| 12 | pass |
|---|
| 13 | try: |
|---|
| 14 | import gtk |
|---|
| 15 | import gtk.glade |
|---|
| 16 | import gobject |
|---|
| 17 | except: |
|---|
| 18 | sys.exit(1) |
|---|
| 19 | |
|---|
| 20 | |
|---|
| 21 | ## Translation stuff ## |
|---|
| 22 | ## Adapted from http://www.learningpython.com/2006/12/03/translating-your-pythonpygtk-application/ |
|---|
| 23 | |
|---|
| 24 | import locale |
|---|
| 25 | import gettext |
|---|
| 26 | |
|---|
| 27 | APP_NAME="conseil" |
|---|
| 28 | #Get the local directory since we are not installing anything |
|---|
| 29 | local_path = os.path.realpath(os.path.dirname(sys.argv[0])) |
|---|
| 30 | locale_path = os.path.join(local_path, "locale") |
|---|
| 31 | # Init the list of languages to support |
|---|
| 32 | langs = [] |
|---|
| 33 | #Check the default locale |
|---|
| 34 | lc, encoding = locale.getdefaultlocale() |
|---|
| 35 | if (lc): |
|---|
| 36 | #If we have a default, it's the first in the list |
|---|
| 37 | langs = [lc] |
|---|
| 38 | # Now lets get all of the supported languages on the system |
|---|
| 39 | language = os.environ.get('LANGUAGE', None) |
|---|
| 40 | if (language): |
|---|
| 41 | """langage comes back something like en_CA:en_US:en_GB:en |
|---|
| 42 | on linuxy systems, so we need to split it up into a list""" |
|---|
| 43 | langs += language.split(":") |
|---|
| 44 | else: |
|---|
| 45 | language = os.environ.get('LANG', None) |
|---|
| 46 | if (language): |
|---|
| 47 | langs += language.split(":") |
|---|
| 48 | """Now add on to the back of the list the translations that we |
|---|
| 49 | know that we have, our defaults""" |
|---|
| 50 | langs += ["en_US"] |
|---|
| 51 | |
|---|
| 52 | """Now langs is a list of all of the languages that we are going |
|---|
| 53 | to try to use. First we check the default, then what the system |
|---|
| 54 | told us, and finally the 'known' list""" |
|---|
| 55 | |
|---|
| 56 | gettext.bindtextdomain(APP_NAME, locale_path) |
|---|
| 57 | gettext.textdomain(APP_NAME) |
|---|
| 58 | gtk.glade.bindtextdomain(APP_NAME, locale_path) |
|---|
| 59 | gtk.glade.textdomain(APP_NAME) |
|---|
| 60 | # Get the language to use |
|---|
| 61 | lang = gettext.translation(APP_NAME,local_path |
|---|
| 62 | , languages=langs, fallback = True) |
|---|
| 63 | """Install the language, map _() (which we marked our |
|---|
| 64 | strings to translate with) to lang.gettext() which will |
|---|
| 65 | translate them.""" |
|---|
| 66 | _ = lang.gettext |
|---|
| 67 | |
|---|
| 68 | ## End of translation stuff ## |
|---|
| 69 | |
|---|
| 70 | from protocol_launchpadstaging_web import protocol |
|---|
| 71 | from bug import bug |
|---|
| 72 | from bookmarks_store import * |
|---|
| 73 | from definitions import * |
|---|
| 74 | from management import * |
|---|
| 75 | from retriever import * |
|---|
| 76 | |
|---|
| 77 | class MainWindow : |
|---|
| 78 | |
|---|
| 79 | #Initialization of the main window |
|---|
| 80 | def __init__(self): |
|---|
| 81 | #Set the Glade file |
|---|
| 82 | self.full_glade = gtk.glade.XML("conseil.glade") |
|---|
| 83 | self.main_window = self.full_glade.get_widget("main_window") |
|---|
| 84 | self.bugs_tabs = self.full_glade.get_widget("bugs_notebook") |
|---|
| 85 | self.statusbar = self.full_glade.get_widget("statusbar") |
|---|
| 86 | self.sidebar = self.full_glade.get_widget("sidebar") |
|---|
| 87 | self.accounts = manage_accounts(self.full_glade) |
|---|
| 88 | #self.display_sidebar_menu = self.full_glade.get_widget("sidebar_display") |
|---|
| 89 | #hpane = self.full_glade.get_widget("hpaned1") |
|---|
| 90 | #hpane.set_property("min-position", 100) |
|---|
| 91 | #We remove directly the first page |
|---|
| 92 | # we might want to keep it and remove it at the first action |
|---|
| 93 | self.bugs_tabs.remove_page(0) |
|---|
| 94 | #Create our dictionay and connect it |
|---|
| 95 | dic = { "on_simple_search_button_clicked" : self.simpleSearch_clicked, |
|---|
| 96 | "on_main_window_destroy" : gtk.main_quit, |
|---|
| 97 | "on_bookmarks_row_activated": self.bookmarkClicked, |
|---|
| 98 | "on_add_bookmark_button_clicked" : self.addBookmark_clicked, |
|---|
| 99 | "on_remove_bookmark_button_clicked" : self.removeBookmark_clicked, |
|---|
| 100 | "on_bookmarks_button_press_event" : self.bookmarks_button_press, |
|---|
| 101 | "on_quit_activate": gtk.main_quit, |
|---|
| 102 | "on_import_activate" : self.import_bookmarks, |
|---|
| 103 | "on_open_activate" : self.open_bookmarks, |
|---|
| 104 | "on_account_activate" : self.accounts.show, |
|---|
| 105 | "on_close_accountsconfig_button_clicked" : self.accounts.hide, |
|---|
| 106 | "on_accounts_friendlyname_changed" : self.accounts.friendlyname_change, |
|---|
| 107 | "on_accounts_protocol_changed" : self.accounts.protocol_change, |
|---|
| 108 | "on_accounts_url_changed" : self.accounts.url_change, |
|---|
| 109 | "on_accounts_login_changed" : self.accounts.login_change, |
|---|
| 110 | "on_accounts_password_changed" : self.accounts.password_change, |
|---|
| 111 | "on_accounts_add" : self.add_account, |
|---|
| 112 | "on_accounts_remove" : self.remove_account, |
|---|
| 113 | "on_a_propos_activate" : self.about_dialog, |
|---|
| 114 | "on_aboutdialog_response" : lambda x, y: x.hide(), |
|---|
| 115 | "on_bugs_notebook_switch_page" : self.switch_page, |
|---|
| 116 | "on_sidebar_display_activate" : self.show_sidebar, |
|---|
| 117 | "on_sidebar_close_button_clicked" : lambda close : self.set_sidebar(0), |
|---|
| 118 | "on_aboutdialog_delete_event" : self.close_dialog, |
|---|
| 119 | "on_accounts_dialog_delete_event" : self.close_dialog |
|---|
| 120 | } |
|---|
| 121 | self.full_glade.signal_autoconnect(dic) |
|---|
| 122 | #self.sidebar_bm.connect("button_press_event", self.bookmarks_button_press) |
|---|
| 123 | #Wich BTS are we using ? |
|---|
| 124 | #self.auth = user() |
|---|
| 125 | #bookmarks init |
|---|
| 126 | #construction of the bookmarks widget |
|---|
| 127 | self.bookmarksStorage = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING) # title, bts name, xml |
|---|
| 128 | self.bookmarksTreeV = self.full_glade.get_widget("bookmarks_list") |
|---|
| 129 | self.bookmarksTreeV.set_model(self.bookmarksStorage) |
|---|
| 130 | self.renderer = gtk.CellRendererText() |
|---|
| 131 | self.renderer.set_property('editable', False) |
|---|
| 132 | self.renderer.connect('edited', self.__edited_callback) |
|---|
| 133 | column = gtk.TreeViewColumn("title", self.renderer, text=0) |
|---|
| 134 | self.bookmarksTreeV.append_column(column) |
|---|
| 135 | #~ self.bookmarksTreeV.append_column(gtk.TreeViewColumn("bts", gtk.CellRendererText(), text=1)) |
|---|
| 136 | #~ self.bookmarksTreeV.append_column(gtk.TreeViewColumn("bug_id", gtk.CellRendererText(), text=2)) |
|---|
| 137 | self.bookmarksTreeV.show() |
|---|
| 138 | #construction of the advanced search widget |
|---|
| 139 | #with scrollbars |
|---|
| 140 | #adv_main = self.full_glade.get_widget("adv_search_scrolled") |
|---|
| 141 | #or without |
|---|
| 142 | adv_main = self.full_glade.get_widget("adv_search_box") |
|---|
| 143 | adv_parent = self.full_glade.get_widget("adv_search_side_box") |
|---|
| 144 | to_remove = self.full_glade.get_widget("omega13") |
|---|
| 145 | |
|---|
| 146 | statusbox = self.full_glade.get_widget("adv_search_status_box") |
|---|
| 147 | for e in self.accounts[0].bts().availableStatus(): # fixme ! |
|---|
| 148 | checkbox = gtk.CheckButton(label=e) |
|---|
| 149 | checkbox.show() |
|---|
| 150 | statusbox.pack_start(checkbox,expand=0,padding=1) |
|---|
| 151 | importancebox = self.full_glade.get_widget("adv_search_importance_box") |
|---|
| 152 | for e in self.accounts[0].bts().availableImportance(): # fixme ! |
|---|
| 153 | checkbox = gtk.CheckButton(label=e) |
|---|
| 154 | checkbox.show() |
|---|
| 155 | importancebox.pack_start(checkbox,expand=0,padding=1) |
|---|
| 156 | |
|---|
| 157 | viewport = self.full_glade.get_widget("adv_search_viewport") |
|---|
| 158 | viewport.remove(adv_main) |
|---|
| 159 | |
|---|
| 160 | adv_parent.remove(to_remove) |
|---|
| 161 | adv_parent.add(adv_main) |
|---|
| 162 | |
|---|
| 163 | self.bookmarks = {} |
|---|
| 164 | for account in self.accounts: |
|---|
| 165 | bts = account.bts().btsName() |
|---|
| 166 | if not bts in self.bookmarks: |
|---|
| 167 | self.bookmarks[bts] = bookmarks_store(bts, self.bookmarksStorage) |
|---|
| 168 | |
|---|
| 169 | self.btslist = btslist = self.full_glade.get_widget('bts_list1') |
|---|
| 170 | cell = gtk.CellRendererText() |
|---|
| 171 | model = gtk.ListStore(gobject.TYPE_STRING) |
|---|
| 172 | btslist.set_model(model) |
|---|
| 173 | btslist.pack_start(cell) |
|---|
| 174 | btslist.set_attributes(cell, text=0) |
|---|
| 175 | for bts in self.bookmarks: |
|---|
| 176 | btslist.append_text(bts) |
|---|
| 177 | btslist.set_active(0) |
|---|
| 178 | |
|---|
| 179 | def add_account(self, x): |
|---|
| 180 | bts = self.accounts.add_account().bts().btsName() |
|---|
| 181 | if not bts in self.bookmarks: |
|---|
| 182 | self.bookmarks[bts] = bookmarks_store(bts, self.bookmarksStorage) |
|---|
| 183 | |
|---|
| 184 | def remove_account(self, x): |
|---|
| 185 | acc = self.accounts.selected_account() |
|---|
| 186 | if acc: |
|---|
| 187 | bts = acc.bts().btsName() |
|---|
| 188 | self.accounts.remove_account() |
|---|
| 189 | if len(self.get_accounts_for_bts(bts)) == 1: |
|---|
| 190 | del self.booksmarks[bts] |
|---|
| 191 | |
|---|
| 192 | def __isint(self,x): |
|---|
| 193 | try: |
|---|
| 194 | x = int(x) |
|---|
| 195 | return 1 |
|---|
| 196 | except: |
|---|
| 197 | return 0 |
|---|
| 198 | |
|---|
| 199 | def show_sidebar(self,check) : |
|---|
| 200 | self.set_sidebar(check.get_active()) |
|---|
| 201 | |
|---|
| 202 | def set_sidebar(self,boolean) : |
|---|
| 203 | menu = self.full_glade.get_widget("sidebar_display") |
|---|
| 204 | if boolean : |
|---|
| 205 | self.sidebar.show() |
|---|
| 206 | menu.set_active(1) |
|---|
| 207 | else: |
|---|
| 208 | self.sidebar.hide() |
|---|
| 209 | menu.set_active(0) |
|---|
| 210 | |
|---|
| 211 | def switch_page(self,notebook,pointer,position) : |
|---|
| 212 | widget = notebook.get_nth_page(position) |
|---|
| 213 | descr = tab_descr(widget) |
|---|
| 214 | title = read_title(descr) |
|---|
| 215 | zetype = read_type(descr) |
|---|
| 216 | if zetype == 2 or zetype == 3 : |
|---|
| 217 | try : |
|---|
| 218 | ztree = widget.get_children()[0].get_children()[0].get_children()[0] |
|---|
| 219 | model= ztree.get_model() |
|---|
| 220 | total = str(model.iter_n_children(None)) |
|---|
| 221 | title = "search for %s - (%s results)" %(title,total) |
|---|
| 222 | #eek.. this is ugly, isn't it ? |
|---|
| 223 | except : |
|---|
| 224 | title = _("search for %s") %title |
|---|
| 225 | self.statusbar.pop(0) |
|---|
| 226 | self.statusbar.push(0,title) |
|---|
| 227 | |
|---|
| 228 | #bookmark is edited |
|---|
| 229 | def __edited_callback(self, cellrenderertext, path, new_text) : |
|---|
| 230 | self.bookmarks.rename(path,new_text) |
|---|
| 231 | self.renderer.set_property('editable', False) |
|---|
| 232 | |
|---|
| 233 | def import_bookmarks(self,a) : |
|---|
| 234 | def callback(dial,zeid,bmarks): |
|---|
| 235 | if zeid == -5 : |
|---|
| 236 | if bmarks.import_file(dial.get_filename()) == 0 : |
|---|
| 237 | print _("Not valid Conseil XML file") |
|---|
| 238 | dial.destroy() |
|---|
| 239 | else : |
|---|
| 240 | dial.destroy() |
|---|
| 241 | dialog = gtk.FileChooserDialog() |
|---|
| 242 | cancel_button = dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) |
|---|
| 243 | ok_button = dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK) |
|---|
| 244 | ok_button.grab_default() |
|---|
| 245 | dialog.connect("response", callback, self.bookmarks) |
|---|
| 246 | dialog.show() |
|---|
| 247 | |
|---|
| 248 | def open_bookmarks(self,a): |
|---|
| 249 | def callback(dial,zeid,bmarks): |
|---|
| 250 | if zeid == -5 : |
|---|
| 251 | for i in bmarks.open_file(dial.get_filename()) : |
|---|
| 252 | self.open_page(bookmark(None,i)) |
|---|
| 253 | dial.destroy() |
|---|
| 254 | else : |
|---|
| 255 | dial.destroy() |
|---|
| 256 | dialog = gtk.FileChooserDialog() |
|---|
| 257 | cancel_button = dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) |
|---|
| 258 | ok_button = dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK) |
|---|
| 259 | ok_button.grab_default() |
|---|
| 260 | dialog.connect("response", callback, self.bookmarks) |
|---|
| 261 | dialog.show() |
|---|
| 262 | |
|---|
| 263 | |
|---|
| 264 | |
|---|
| 265 | |
|---|
| 266 | def bookmarks_button_press(self,widget, event) : |
|---|
| 267 | #middle click, we open the bug |
|---|
| 268 | if event.button == 2 : |
|---|
| 269 | zetuple = widget.get_selection().get_selected() |
|---|
| 270 | #Fixme : what if the user didn't click on the tree first ? |
|---|
| 271 | if zetuple[1] != None : |
|---|
| 272 | clicked = bookmark(widget,None,zetuple[2]) |
|---|
| 273 | self.open_page(clicked) |
|---|
| 274 | |
|---|
| 275 | #right click menu |
|---|
| 276 | elif event.button == 3 : |
|---|
| 277 | # callback for delete right click menu |
|---|
| 278 | def del_callback(menuitem) : |
|---|
| 279 | zetuple = widget.get_selection().get_selected() |
|---|
| 280 | self.bookmarks.delete(zetuple[1]) |
|---|
| 281 | #callback for rename TODO |
|---|
| 282 | def ren_callback(menuitem) : |
|---|
| 283 | self.renderer.set_property('editable', True) |
|---|
| 284 | zetuple = widget.get_selection().get_selected() |
|---|
| 285 | store = zetuple[0] |
|---|
| 286 | path = store.get_string_from_iter(zetuple[1]) |
|---|
| 287 | column = widget.get_column(0) |
|---|
| 288 | widget.set_cursor(path,column,True) |
|---|
| 289 | #définition du right click menu |
|---|
| 290 | # TODO : use a widget as the MenuItem |
|---|
| 291 | delete = gtk.MenuItem("delete") |
|---|
| 292 | delete.connect("activate", del_callback) |
|---|
| 293 | delete.show() |
|---|
| 294 | rename = gtk.MenuItem("rename") |
|---|
| 295 | rename.connect("activate", ren_callback) |
|---|
| 296 | rename.show() |
|---|
| 297 | menu = gtk.Menu() |
|---|
| 298 | menu.append(delete) |
|---|
| 299 | menu.append(rename) |
|---|
| 300 | menu.popup(None, None, None, event.button, event.time) |
|---|
| 301 | |
|---|
| 302 | |
|---|
| 303 | def bookmarkClicked(self, widget, path, view_column): |
|---|
| 304 | clicked = bookmark(widget,None,self.bookmarksStorage[path][2]) |
|---|
| 305 | self.open_page(clicked) |
|---|
| 306 | |
|---|
| 307 | #open a page in the given widget using clicked, which is a bookmark object |
|---|
| 308 | def open_page(self,clicked) : |
|---|
| 309 | accs = self.get_accounts_for_bts(clicked.bts) |
|---|
| 310 | retriever = accs[0].retriever(self.bugs_tabs, self.full_glade) |
|---|
| 311 | |
|---|
| 312 | method = clicked.get_method() |
|---|
| 313 | dic = makedict() |
|---|
| 314 | if method == 1: |
|---|
| 315 | # besoin de programmation défensive ici FIXME |
|---|
| 316 | dic["method"] = 1 |
|---|
| 317 | bug_nbr = clicked.get_bug() |
|---|
| 318 | dic["bug"] = bug_nbr |
|---|
| 319 | retriever.display(dic) |
|---|
| 320 | elif method == 2: |
|---|
| 321 | dic["method"] = 2 |
|---|
| 322 | searchString = clicked.get_search() |
|---|
| 323 | dic["search"] = searchString |
|---|
| 324 | retriever.display(dic) |
|---|
| 325 | elif method == 3: |
|---|
| 326 | dic["method"] = 3 |
|---|
| 327 | product = clicked.get_product() |
|---|
| 328 | dic["product"] = product |
|---|
| 329 | search = clicked.get_search() |
|---|
| 330 | dic["search"] = search |
|---|
| 331 | retriever.display(dic) |
|---|
| 332 | |
|---|
| 333 | #when the "add bookmark button is clicked |
|---|
| 334 | def addBookmark_clicked(self, widget): |
|---|
| 335 | tab = self.bugs_tabs.get_nth_page(self.bugs_tabs.get_current_page()) |
|---|
| 336 | if tab != None : |
|---|
| 337 | #tab_descr function is part of page |
|---|
| 338 | descr = tab_descr(tab) |
|---|
| 339 | self.bookmarks[read_bts(descr)].add(descr) |
|---|
| 340 | |
|---|
| 341 | #the "remove bookmark" button is clicked |
|---|
| 342 | def removeBookmark_clicked(self, widget): |
|---|
| 343 | to_delete = self.bookmarksTreeV.get_selection().get_selected()[1] |
|---|
| 344 | bts = self.bookmarksStorage.get_value(to_delete, 2) |
|---|
| 345 | self.bookmarks[bts].delete(to_delete) |
|---|
| 346 | |
|---|
| 347 | #main search button pressed |
|---|
| 348 | def simpleSearch_clicked(self, widget): |
|---|
| 349 | acc = self.get_accounts_for_bts(self.btslist.get_active_text())[0] |
|---|
| 350 | bts = acc.bts() |
|---|
| 351 | retriever = acc.retriever(self.bugs_tabs, self.full_glade) |
|---|
| 352 | |
|---|
| 353 | searchString = self.full_glade.get_widget("simple_search_entry").get_text().strip() |
|---|
| 354 | if searchString.startswith("#") : |
|---|
| 355 | tmp = searchString.lstrip("#") |
|---|
| 356 | if self.__isint(tmp) : |
|---|
| 357 | searchString = tmp |
|---|
| 358 | if searchString != '' : |
|---|
| 359 | dic = makedict() |
|---|
| 360 | dic["bts"] = bts.btsName() |
|---|
| 361 | # Have we a single bug number ? |
|---|
| 362 | if self.__isint(searchString) : |
|---|
| 363 | dic["method"] = 1 |
|---|
| 364 | dic["bug"] = searchString |
|---|
| 365 | retriever.display(dic) |
|---|
| 366 | # no, we have a search. |
|---|
| 367 | else : |
|---|
| 368 | array = string.split(searchString,' ',1) |
|---|
| 369 | if bts.packageExist(array[0]) : |
|---|
| 370 | dic["method"]= 3 |
|---|
| 371 | dic["product"] = array[0] |
|---|
| 372 | if len(array) < 2 : |
|---|
| 373 | dic["search"] = None |
|---|
| 374 | else : |
|---|
| 375 | dic["search"] = array[1] |
|---|
| 376 | retriever.display(dic) |
|---|
| 377 | else : |
|---|
| 378 | dic["method"]= 2 |
|---|
| 379 | dic["search"] = searchString |
|---|
| 380 | retriever.display(dic) |
|---|
| 381 | |
|---|
| 382 | def about_dialog(self,widget): |
|---|
| 383 | about_widget = self.full_glade.get_widget("aboutdialog") |
|---|
| 384 | about_widget.show() |
|---|
| 385 | |
|---|
| 386 | def close_dialog(self,widget,event) : |
|---|
| 387 | widget.hide() |
|---|
| 388 | return True |
|---|
| 389 | |
|---|
| 390 | def get_accounts_for_bts(self, bts): |
|---|
| 391 | accounts = [] |
|---|
| 392 | for account in self.accounts: |
|---|
| 393 | if account.bts().btsName() == bts: |
|---|
| 394 | accounts.append(account) |
|---|
| 395 | return accounts |
|---|
| 396 | |
|---|
| 397 | if __name__ == "__main__": |
|---|
| 398 | gobject.threads_init() |
|---|
| 399 | #gtk.gdk.threads_init() |
|---|
| 400 | hwg = MainWindow() |
|---|
| 401 | #gtk.threads_enter() |
|---|
| 402 | gtk.main() |
|---|
| 403 | #gtk.threads_leave() |
|---|