X-Git-Url: http://git.maemo.org/git/?a=blobdiff_plain;f=src%2Fgv_views.py;h=9b56bee50e75f5b7085531b3f0e47bd0e0e3f4b4;hb=e9f715b4a8367af16cb6586a6927e9846c477310;hp=7915223bab86248f5d575d30eda98b94014ad02b;hpb=7d15d38ddddcecc8ac7864f6220a7b49c9cd4bb1;p=gc-dialer diff --git a/src/gv_views.py b/src/gv_views.py index 7915223..9b56bee 100644 --- a/src/gv_views.py +++ b/src/gv_views.py @@ -18,6 +18,7 @@ You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +@todo Collapse voicemails @todo Alternate UI for dialogs (stackables) """ @@ -116,6 +117,54 @@ def abbrev_relative_date(date): return "%s %s" % (parts[0], parts[1][0]) +def _collapse_message(messageLines, maxCharsPerLine, maxLines): + lines = 0 + + numLines = len(messageLines) + for line in messageLines[0:min(maxLines, numLines)]: + linesPerLine = max(1, int(len(line) / maxCharsPerLine)) + allowedLines = maxLines - lines + acceptedLines = min(allowedLines, linesPerLine) + acceptedChars = acceptedLines * maxCharsPerLine + + if acceptedChars < (len(line) + 3): + suffix = "..." + else: + acceptedChars = len(line) # eh, might as well complete the line + suffix = "" + abbrevMessage = "%s%s" % (line[0:acceptedChars], suffix) + yield abbrevMessage + + lines += acceptedLines + if maxLines <= lines: + break + + +def collapse_message(message, maxCharsPerLine, maxLines): + r""" + >>> collapse_message("Hello", 60, 2) + 'Hello' + >>> collapse_message("Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789", 60, 2) + 'Hello world how are you doing today? 01234567890123456789012...' + >>> collapse_message('''Hello world how are you doing today? + ... 01234567890123456789 + ... 01234567890123456789 + ... 01234567890123456789 + ... 01234567890123456789''', 60, 2) + 'Hello world how are you doing today?\n01234567890123456789' + >>> collapse_message(''' + ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789 + ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789 + ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789 + ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789 + ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789 + ... Hello world how are you doing today? 01234567890123456789012345678901234567890123456789012345678901234567890123456789''', 60, 2) + '\nHello world how are you doing today? 01234567890123456789012...' + """ + messageLines = message.split("\n") + return "\n".join(_collapse_message(messageLines, maxCharsPerLine, maxLines)) + + class MergedAddressBook(object): """ Merger of all addressbooks @@ -271,7 +320,6 @@ class SmsEntryDialog(object): """ ACTION_CANCEL = "cancel" - ACTION_SELECT = "select" ACTION_DIAL = "dial" ACTION_SEND_SMS = "sms" @@ -286,8 +334,6 @@ class SmsEntryDialog(object): self._smsButton.connect("clicked", self._on_send) self._dialButton = self._widgetTree.get_widget("dialButton") self._dialButton.connect("clicked", self._on_dial) - self._selectButton = self._widgetTree.get_widget("selectButton") - self._selectButton.connect("clicked", self._on_select) self._cancelButton = self._widgetTree.get_widget("cancelSmsButton") self._cancelButton.connect("clicked", self._on_cancel) @@ -295,7 +341,10 @@ class SmsEntryDialog(object): self._messagemodel = gtk.ListStore(gobject.TYPE_STRING) self._messagesView = self._widgetTree.get_widget("smsMessages") - self._scrollWindow = self._messagesView.get_parent() + + self._conversationView = self._messagesView.get_parent() + self._conversationViewPort = self._conversationView.get_parent() + self._scrollWindow = self._conversationViewPort.get_parent() self._phoneButton = self._widgetTree.get_widget("phoneTypeSelection") self._smsEntry = self._widgetTree.get_widget("smsEntry") @@ -305,7 +354,7 @@ class SmsEntryDialog(object): self._numberIndex = -1 self._contactDetails = [] - def run(self, contactDetails, messages = (), parent = None): + def run(self, contactDetails, messages = (), parent = None, defaultIndex = -1): entryConnectId = self._smsEntry.get_buffer().connect("changed", self._on_entry_changed) phoneConnectId = self._phoneButton.connect("clicked", self._on_phone) keyConnectId = self._keyPressEventId = self._dialog.connect("key-press-event", self._on_key_press) @@ -317,13 +366,11 @@ class SmsEntryDialog(object): row = (phoneNumber, display) self._contactDetails.append(row) if 0 < len(self._contactDetails): - self._numberIndex = 0 - self._phoneButton.set_label(self._contactDetails[0][1]) - self._phoneButton.set_sensitive(True) + self._numberIndex = defaultIndex if defaultIndex != -1 else 0 + self._phoneButton.set_label(self._contactDetails[self._numberIndex][1]) else: self._numberIndex = -1 self._phoneButton.set_label("Error: No Number Available") - self._phoneButton.set_sensitive(False) # Add the column to the messages tree view self._messagemodel.clear() @@ -357,17 +404,28 @@ class SmsEntryDialog(object): if parent is not None: self._dialog.set_transient_for(parent) + parentSize = parent.get_size() + self._dialog.resize(parentSize[0], max(parentSize[1]-10, 100)) # Run try: - self._dialog.show() - if messages: - self._messagesView.scroll_to_cell((len(messages)-1, )) + self._dialog.show_all() self._smsEntry.grab_focus() + adjustment = self._scrollWindow.get_vadjustment() + dx = self._conversationView.get_allocation().height - self._conversationViewPort.get_allocation().height + dx = max(dx, 0) + adjustment.value = dx + + if 1 < len(self._contactDetails): + if defaultIndex == -1: + self._request_number() + self._phoneButton.set_sensitive(True) + else: + self._phoneButton.set_sensitive(False) userResponse = self._dialog.run() finally: - self._dialog.hide() + self._dialog.hide_all() # Process the users response if userResponse == gtk.RESPONSE_OK and 0 <= self._numberIndex: @@ -398,6 +456,7 @@ class SmsEntryDialog(object): def _update_letter_count(self, *args): entryLength = self._smsEntry.get_buffer().get_char_count() + charsLeft = self.MAX_CHAR - entryLength self._letterCountLabel.set_text(str(charsLeft)) if charsLeft < 0 or charsLeft == self.MAX_CHAR: @@ -405,7 +464,12 @@ class SmsEntryDialog(object): else: self._smsButton.set_sensitive(True) - def _on_phone(self, *args): + if entryLength == 0: + self._dialButton.set_sensitive(True) + else: + self._dialButton.set_sensitive(False) + + def _request_number(self): try: assert 0 <= self._numberIndex, "%r" % self._numberIndex @@ -419,20 +483,20 @@ class SmsEntryDialog(object): except Exception, e: _moduleLogger.exception("%s" % str(e)) + def _on_phone(self, *args): + self._request_number() + def _on_entry_changed(self, *args): self._update_letter_count() def _on_send(self, *args): self._dialog.response(gtk.RESPONSE_OK) + self._action = self.ACTION_SEND_SMS def _on_dial(self, *args): self._dialog.response(gtk.RESPONSE_OK) self._action = self.ACTION_DIAL - def _on_select(self, *args): - self._dialog.response(gtk.RESPONSE_OK) - self._action = self.ACTION_SELECT - def _on_cancel(self, *args): self._dialog.response(gtk.RESPONSE_CANCEL) self._action = self.ACTION_CANCEL @@ -620,7 +684,7 @@ class AccountInfo(object): self._applyAlarmTimeoutId = None self._window = gtk_toolbox.find_parent_window(self._minutesEntryButton) - self._defaultCallback = "" + self._callbackNumber = "" def enable(self): assert self._backend.is_authed(), "Attempting to enable backend while not logged in" @@ -630,6 +694,7 @@ class AccountInfo(object): del self._callbackList[:] self._onCallbackSelectChangedId = self._callbackSelectButton.connect("clicked", self._on_callbackentry_clicked) + self._set_callback_label("") if self._alarmHandler is not None: self._notifyCheckbox.set_active(self._alarmHandler.isEnabled) @@ -655,6 +720,7 @@ class AccountInfo(object): def disable(self): self._callbackSelectButton.disconnect(self._onCallbackSelectChangedId) self._onCallbackSelectChangedId = 0 + self._set_callback_label("") if self._alarmHandler is not None: self._notifyCheckbox.disconnect(self._onNotifyToggled) @@ -677,13 +743,6 @@ class AccountInfo(object): self.clear() del self._callbackList[:] - def get_selected_callback_number(self): - currentLabel = self._callbackSelectButton.get_label() - if currentLabel is not None: - return make_ugly(currentLabel) - else: - return "" - def set_account_number(self, number): """ Displays current account number @@ -698,7 +757,7 @@ class AccountInfo(object): return True def clear(self): - self._callbackSelectButton.set_label("No Callback Number") + self._set_callback_label("") self.set_account_number("") self._isPopulated = False @@ -710,7 +769,7 @@ class AccountInfo(object): return "Account Info" def load_settings(self, config, section): - self._defaultCallback = config.get(section, "callback") + self._callbackNumber = make_ugly(config.get(section, "callback")) self._notifyOnMissed = config.getboolean(section, "notifyOnMissed") self._notifyOnVoicemail = config.getboolean(section, "notifyOnVoicemail") self._notifyOnSms = config.getboolean(section, "notifyOnSms") @@ -719,8 +778,7 @@ class AccountInfo(object): """ @note Thread Agnostic """ - callback = self.get_selected_callback_number() - config.set(section, "callback", callback) + config.set(section, "callback", self._callbackNumber) config.set(section, "notifyOnMissed", repr(self._notifyOnMissed)) config.set(section, "notifyOnVoicemail", repr(self._notifyOnVoicemail)) config.set(section, "notifyOnSms", repr(self._notifyOnSms)) @@ -741,7 +799,7 @@ class AccountInfo(object): for number, description in callbackNumbers.iteritems(): self._callbackList.append((make_pretty(number), description)) - self._set_callback_number(self._defaultCallback) + self._set_callback_number(self._callbackNumber) def _set_callback_number(self, number): try: @@ -753,15 +811,14 @@ class AccountInfo(object): self._backend.get_callback_number(), ), ) + self._set_callback_label(number) else: self._backend.set_callback_number(number) assert make_ugly(number) == make_ugly(self._backend.get_callback_number()), "Callback number should be %s but instead is %s" % ( make_pretty(number), make_pretty(self._backend.get_callback_number()) ) - prettyNumber = make_pretty(number) - if len(prettyNumber) == 0: - prettyNumber = "No Callback Number" - self._callbackSelectButton.set_label(prettyNumber) + self._callbackNumber = make_ugly(number) + self._set_callback_label(number) _moduleLogger.info( "Callback number set to %s" % ( self._backend.get_callback_number(), @@ -770,6 +827,12 @@ class AccountInfo(object): except Exception, e: self._errorDisplay.push_exception() + def _set_callback_label(self, uglyNumber): + prettyNumber = make_pretty(uglyNumber) + if len(prettyNumber) == 0: + prettyNumber = "No Callback Number" + self._callbackSelectButton.set_label(prettyNumber) + def _update_alarm_settings(self, recurrence): try: isEnabled = self._notifyCheckbox.get_active() @@ -782,7 +845,7 @@ class AccountInfo(object): def _on_callbackentry_clicked(self, *args): try: - actualSelection = make_pretty(self.get_selected_callback_number()) + actualSelection = make_pretty(self._callbackNumber) userOptions = dict( (number, "%s (%s)" % (number, description)) @@ -891,6 +954,7 @@ class RecentCallsView(object): DATE_IDX = 1 ACTION_IDX = 2 FROM_IDX = 3 + FROM_ID_IDX = 4 def __init__(self, widgetTree, backend, errorDisplay): self._errorDisplay = errorDisplay @@ -902,6 +966,7 @@ class RecentCallsView(object): gobject.TYPE_STRING, # date gobject.TYPE_STRING, # action gobject.TYPE_STRING, # from + gobject.TYPE_STRING, # from id ) self._recentview = widgetTree.get_widget("recentview") self._recentviewselection = None @@ -955,7 +1020,7 @@ class RecentCallsView(object): self._recentview.append_column(self._numberColumn) self._recentview.append_column(self._nameColumn) self._recentviewselection = self._recentview.get_selection() - self._recentviewselection.set_mode(gtk.SELECTION_NONE) + self._recentviewselection.set_mode(gtk.SELECTION_SINGLE) self._onRecentviewRowActivatedId = self._recentview.connect("row-activated", self._on_recentview_row_activated) @@ -1018,13 +1083,13 @@ class RecentCallsView(object): for data in gv_backend.sort_messages(recentItems) ) - for personName, phoneNumber, date, action in recentItems: + for contactId, personName, phoneNumber, date, action in recentItems: if not personName: personName = "Unknown" date = abbrev_relative_date(date) prettyNumber = phoneNumber[2:] if phoneNumber.startswith("+1") else phoneNumber prettyNumber = make_pretty(prettyNumber) - item = (prettyNumber, date, action.capitalize(), personName) + item = (prettyNumber, date, action.capitalize(), personName, contactId) with gtk_toolbox.gtk_lock(): self._recentmodel.append(item) except Exception, e: @@ -1043,13 +1108,33 @@ class RecentCallsView(object): number = self._recentmodel.get_value(itr, self.NUMBER_IDX) number = make_ugly(number) - contactPhoneNumbers = [("Phone", number)] description = self._recentmodel.get_value(itr, self.FROM_IDX) + contactId = self._recentmodel.get_value(itr, self.FROM_ID_IDX) + if contactId: + contactPhoneNumbers = list(self._backend.get_contact_details(contactId)) + defaultMatches = [ + (number == make_ugly(contactNumber) or number[1:] == make_ugly(contactNumber)) + for (numberDescription, contactNumber) in contactPhoneNumbers + ] + try: + defaultIndex = defaultMatches.index(True) + except ValueError: + contactPhoneNumbers.append(("Other", number)) + defaultIndex = len(contactPhoneNumbers)-1 + _moduleLogger.warn( + "Could not find contact %r's number %s among %r" % ( + contactId, number, contactPhoneNumbers + ) + ) + else: + contactPhoneNumbers = [("Phone", number)] + defaultIndex = -1 action, phoneNumber, message = self._phoneTypeSelector.run( contactPhoneNumbers, messages = (description, ), parent = self._window, + defaultIndex = defaultIndex, ) if action == SmsEntryDialog.ACTION_CANCEL: return @@ -1068,6 +1153,18 @@ class MessagesView(object): HEADER_IDX = 2 MESSAGE_IDX = 3 MESSAGES_IDX = 4 + FROM_ID_IDX = 5 + + NO_MESSAGES = "None" + VOICEMAIL_MESSAGES = "Voicemail" + TEXT_MESSAGES = "Texts" + ALL_MESSAGES = "All Messages" + MESSAGE_TYPES = [NO_MESSAGES, VOICEMAIL_MESSAGES, TEXT_MESSAGES, ALL_MESSAGES] + + UNREAD_STATUS = "Unread" + UNARCHIVED_STATUS = "Unarchived" + ALL_STATUS = "Any" + MESSAGE_STATUSES = [UNREAD_STATUS, UNARCHIVED_STATUS, ALL_STATUS] def __init__(self, widgetTree, backend, errorDisplay): self._errorDisplay = errorDisplay @@ -1080,6 +1177,7 @@ class MessagesView(object): gobject.TYPE_STRING, # header gobject.TYPE_STRING, # message object, # messages + gobject.TYPE_STRING, # from id ) self._messageview = widgetTree.get_widget("messages_view") self._messageviewselection = None @@ -1096,6 +1194,13 @@ class MessagesView(object): self._window = gtk_toolbox.find_parent_window(self._messageview) self._phoneTypeSelector = SmsEntryDialog(widgetTree) + self._messageTypeButton = widgetTree.get_widget("messageTypeButton") + self._onMessageTypeClickedId = 0 + self._messageType = self.ALL_MESSAGES + self._messageStatusButton = widgetTree.get_widget("messageStatusButton") + self._onMessageStatusClickedId = 0 + self._messageStatus = self.ALL_STATUS + self._updateSink = gtk_toolbox.threaded_stage( gtk_toolbox.comap( self._idly_populate_messageview, @@ -1111,12 +1216,25 @@ class MessagesView(object): self._messageview.append_column(self._messageColumn) self._messageviewselection = self._messageview.get_selection() - self._messageviewselection.set_mode(gtk.SELECTION_NONE) + self._messageviewselection.set_mode(gtk.SELECTION_SINGLE) + + self._messageTypeButton.set_label(self._messageType) + self._messageStatusButton.set_label(self._messageStatus) - self._onMessageviewRowActivatedId = self._messageview.connect("row-activated", self._on_messageview_row_activated) + self._onMessageviewRowActivatedId = self._messageview.connect( + "row-activated", self._on_messageview_row_activated + ) + self._onMessageTypeClickedId = self._messageTypeButton.connect( + "clicked", self._on_message_type_clicked + ) + self._onMessageStatusClickedId = self._messageStatusButton.connect( + "clicked", self._on_message_status_clicked + ) def disable(self): self._messageview.disconnect(self._onMessageviewRowActivatedId) + self._messageTypeButton.disconnect(self._onMessageTypeClickedId) + self._messageStatusButton.disconnect(self._onMessageStatusClickedId) self.clear() @@ -1143,17 +1261,44 @@ class MessagesView(object): def name(): return "Messages" - def load_settings(self, config, section): - pass + def load_settings(self, config, sectionName): + try: + self._messageStatus = config.get(sectionName, "status") + self._messageType = config.get(sectionName, "type") + except ConfigParser.NoOptionError: + pass - def save_settings(self, config, section): + def save_settings(self, config, sectionName): """ @note Thread Agnostic """ - pass + config.set(sectionName, "status", self._messageStatus) + config.set(sectionName, "type", self._messageType) _MIN_MESSAGES_SHOWN = 4 + @classmethod + def _filter_messages(cls, message, type, status): + if type == cls.ALL_MESSAGES: + isType = True + else: + messageType = message["type"] + isType = messageType == type + + if status == cls.ALL_STATUS: + isStatus = True + else: + isUnarchived = not message["isArchived"] + isUnread = not message["isRead"] + if status == cls.UNREAD_STATUS: + isStatus = isUnarchived and isUnread + elif status == cls.UNARCHIVED_STATUS: + isStatus = isUnarchived + else: + assert "Status %s is bad for %r" % (status, message) + + return isType and isStatus + def _idly_populate_messageview(self): with gtk_toolbox.gtk_lock(): banner = hildonize.show_busy_banner_start(self._window, "Loading Messages") @@ -1161,19 +1306,23 @@ class MessagesView(object): self._messagemodel.clear() self._isPopulated = True - try: - messageItems = self._backend.get_messages() - except Exception, e: - self._errorDisplay.push_exception_with_lock() - self._isPopulated = False + if self._messageType == self.NO_MESSAGES: messageItems = [] + else: + try: + messageItems = self._backend.get_messages() + except Exception, e: + self._errorDisplay.push_exception_with_lock() + self._isPopulated = False + messageItems = [] messageItems = ( gv_backend.decorate_message(message) for message in gv_backend.sort_messages(messageItems) + if self._filter_messages(message, self._messageType, self._messageStatus) ) - for header, number, relativeDate, messages in messageItems: + for contactId, header, number, relativeDate, messages in messageItems: prettyNumber = number[2:] if number.startswith("+1") else number prettyNumber = make_pretty(prettyNumber) @@ -1187,10 +1336,11 @@ class MessagesView(object): collapsedMessages.extend(messages[-(self._MIN_MESSAGES_SHOWN+0):]) else: collapsedMessages = expandedMessages + #collapsedMessages = _collapse_message(collapsedMessages, 60, self._MIN_MESSAGES_SHOWN) number = make_ugly(number) - row = (number, relativeDate, header, "\n".join(collapsedMessages), expandedMessages) + row = number, relativeDate, header, "\n".join(collapsedMessages), expandedMessages, contactId with gtk_toolbox.gtk_lock(): self._messagemodel.append(row) except Exception, e: @@ -1207,13 +1357,35 @@ class MessagesView(object): if not itr: return - contactPhoneNumbers = [("Phone", self._messagemodel.get_value(itr, self.NUMBER_IDX))] + number = make_ugly(self._messagemodel.get_value(itr, self.NUMBER_IDX)) description = self._messagemodel.get_value(itr, self.MESSAGES_IDX) + contactId = self._messagemodel.get_value(itr, self.FROM_ID_IDX) + if contactId: + contactPhoneNumbers = list(self._backend.get_contact_details(contactId)) + defaultMatches = [ + (number == make_ugly(contactNumber) or number[1:] == make_ugly(contactNumber)) + for (numberDescription, contactNumber) in contactPhoneNumbers + ] + try: + defaultIndex = defaultMatches.index(True) + except ValueError: + contactPhoneNumbers.append(("Other", number)) + defaultIndex = len(contactPhoneNumbers)-1 + _moduleLogger.warn( + "Could not find contact %r's number %s among %r" % ( + contactId, number, contactPhoneNumbers + ) + ) + else: + contactPhoneNumbers = [("Phone", number)] + defaultIndex = -1 + action, phoneNumber, message = self._phoneTypeSelector.run( contactPhoneNumbers, messages = description, parent = self._window, + defaultIndex = defaultIndex, ) if action == SmsEntryDialog.ACTION_CANCEL: return @@ -1224,6 +1396,48 @@ class MessagesView(object): except Exception, e: self._errorDisplay.push_exception() + def _on_message_type_clicked(self, *args, **kwds): + try: + selectedIndex = self.MESSAGE_TYPES.index(self._messageType) + + try: + newSelectedIndex = hildonize.touch_selector( + self._window, + "Message Type", + self.MESSAGE_TYPES, + selectedIndex, + ) + except RuntimeError: + return + + if selectedIndex != newSelectedIndex: + self._messageType = self.MESSAGE_TYPES[newSelectedIndex] + self._messageTypeButton.set_label(self._messageType) + self.update(True) + except Exception, e: + self._errorDisplay.push_exception() + + def _on_message_status_clicked(self, *args, **kwds): + try: + selectedIndex = self.MESSAGE_STATUSES.index(self._messageStatus) + + try: + newSelectedIndex = hildonize.touch_selector( + self._window, + "Message Status", + self.MESSAGE_STATUSES, + selectedIndex, + ) + except RuntimeError: + return + + if selectedIndex != newSelectedIndex: + self._messageStatus = self.MESSAGE_STATUSES[newSelectedIndex] + self._messageStatusButton.set_label(self._messageStatus) + self.update(True) + except Exception, e: + self._errorDisplay.push_exception() + class ContactsView(object): @@ -1276,10 +1490,10 @@ class ContactsView(object): assert self._backend.is_authed(), "Attempting to enable backend while not logged in" self._contactsview.set_model(self._contactsmodel) - self._contactsview.set_fixed_height_mode(True) + self._contactsview.set_fixed_height_mode(False) self._contactsview.append_column(self._contactColumn) self._contactsviewselection = self._contactsview.get_selection() - self._contactsviewselection.set_mode(gtk.SELECTION_NONE) + self._contactsviewselection.set_mode(gtk.SELECTION_SINGLE) del self._booksList[:] for (factoryId, bookId), (factoryName, bookName) in self.get_addressbooks(): @@ -1332,11 +1546,7 @@ class ContactsView(object): def open_addressbook(self, bookFactoryId, bookId): bookFactoryIndex = int(bookFactoryId) addressBook = self._addressBookFactories[bookFactoryIndex].open_addressbook(bookId) - - forceUpdate = True if addressBook is not self._addressBook else False - self._addressBook = addressBook - self.update(force=forceUpdate) def update(self, force = False): if not force and self._isPopulated: @@ -1416,7 +1626,12 @@ class ContactsView(object): selectedFactoryId = self._booksList[newSelectedComboIndex][0] selectedBookId = self._booksList[newSelectedComboIndex][1] + + oldAddressbook = self._addressBook self.open_addressbook(selectedFactoryId, selectedBookId) + forceUpdate = True if oldAddressbook is not self._addressBook else False + self.update(force=forceUpdate) + self._selectedComboIndex = newSelectedComboIndex self._bookSelectionButton.set_label(self._booksList[self._selectedComboIndex][2]) except Exception, e: