diff --git a/gajim/app_actions.py b/gajim/app_actions.py index 2209d0c5ecacf4dc49263abf1f1f12f96223ecb7..18942e6289b5a88bc39561a52e870d6acb7c1ab4 100644 --- a/gajim/app_actions.py +++ b/gajim/app_actions.py @@ -25,10 +25,8 @@ from gajim.gui.dialogs import ShortcutsWindow from gajim.gui.about import AboutDialog -from gajim.gui.history import HistoryWindow from gajim.gui.discovery import ServiceDiscoveryWindow from gajim.gui.util import open_window -from gajim.gui.util import get_app_window # General Actions @@ -310,13 +308,7 @@ def on_browse_history(_action, param): jid = dict_.get('jid') account = dict_.get('account') - window = get_app_window(HistoryWindow) - if window is None: - HistoryWindow(jid, account) - else: - window.present() - if jid is not None and account is not None: - window.open_history(jid, account) + open_window('HistoryWindow', account=account, jid=jid) def on_groupchat_join(_action, param): diff --git a/gajim/common/storage/archive.py b/gajim/common/storage/archive.py index a78131022598eeba4ad8a109b5a3a4a6aab9dcbf..a1b618e4e065c316d74888eb5ecc5ebe929ef78a 100644 --- a/gajim/common/storage/archive.py +++ b/gajim/common/storage/archive.py @@ -420,7 +420,7 @@ def get_last_conversation_line(self, account, jid): return self._con.execute(sql, tuple(jids)).fetchone() @timeit - def get_conversation_for_date(self, account, jid, date): + def get_messages_for_date(self, account, jid, date): """ Load the complete conversation with a given jid on a specific date @@ -435,21 +435,26 @@ def get_conversation_for_date(self, account, jid, date): """ jids = self._get_family_jids(account, jid) + account_id = self.get_account_id(account) delta = datetime.timedelta( hours=23, minutes=59, seconds=59, microseconds=999999) + date_ts = date.timestamp() + delta_ts = (date + delta).timestamp() sql = ''' SELECT contact_name, time, kind, show, message, subject, additional_data, log_line_id FROM logs NATURAL JOIN jids WHERE jid IN ({jids}) + AND account_id = {account_id} AND time BETWEEN ? AND ? - ORDER BY time, log_line_id - '''.format(jids=', '.join('?' * len(jids))) + ORDER BY time DESC, log_line_id DESC + '''.format(jids=', '.join('?' * len(jids)), + account_id=account_id) - return self._con.execute(sql, tuple(jids) + - (date.timestamp(), - (date + delta).timestamp())).fetchall() + return self._con.execute( + sql, + tuple(jids) + (date_ts, delta_ts)).fetchall() @timeit def search_log(self, account, jid, query, date=None): diff --git a/gajim/data/gui/history_window.ui b/gajim/data/gui/history_window.ui index 741c46587ef8370cbcf0d25f361ea10406a90e0e..b1dc2a523b64724ba812f7e097e0b87ec329c2e0 100644 --- a/gajim/data/gui/history_window.ui +++ b/gajim/data/gui/history_window.ui @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> -<!-- Generated with glade 3.36.0 --> +<!-- Generated with glade 3.38.2 --> <interface> - <requires lib="gtk+" version="3.22"/> + <requires lib="gtk+" version="3.24"/> <object class="GtkListStore" id="liststore1"> <columns> <!-- column-name gchararray1 --> @@ -11,25 +11,25 @@ </columns> </object> <object class="GtkPopoverMenu" id="more_menu"> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <child> <object class="GtkBox"> <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_start">12</property> - <property name="margin_end">12</property> - <property name="margin_top">12</property> - <property name="margin_bottom">12</property> + <property name="can-focus">False</property> + <property name="margin-start">12</property> + <property name="margin-end">12</property> + <property name="margin-top">12</property> + <property name="margin-bottom">12</property> <property name="orientation">vertical</property> <property name="spacing">6</property> <child> <object class="GtkCheckButton" id="show_status_checkbutton"> <property name="label" translatable="yes">Display status changes</property> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">False</property> - <property name="draw_indicator">True</property> - <signal name="toggled" handler="on_show_status_checkbutton_toggled" swapped="no"/> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="draw-indicator">True</property> + <signal name="toggled" handler="_on_show_status" swapped="no"/> </object> <packing> <property name="expand">False</property> @@ -40,7 +40,7 @@ <child> <object class="GtkSeparator"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> </object> <packing> <property name="expand">False</property> @@ -51,9 +51,9 @@ <child> <object class="GtkModelButton"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="action_name">app.history-manager</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="action-name">app.history-manager</property> <property name="text" translatable="yes">History Manager</property> </object> <packing> @@ -69,22 +69,38 @@ </packing> </child> </object> + <object class="GtkListStore" id="search_liststore"> + <columns> + <!-- column-name jid --> + <column type="gchararray"/> + <!-- column-name contact_name --> + <column type="gchararray"/> + <!-- column-name date --> + <column type="gchararray"/> + <!-- column-name message --> + <column type="gchararray"/> + <!-- column-name time --> + <column type="gchararray"/> + <!-- column-name log_line_id --> + <column type="gchararray"/> + </columns> + </object> <object class="GtkPopover" id="search_menu"> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <child> <object class="GtkBox"> <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_start">12</property> - <property name="margin_end">12</property> - <property name="margin_top">12</property> - <property name="margin_bottom">12</property> + <property name="can-focus">False</property> + <property name="margin-start">12</property> + <property name="margin-end">12</property> + <property name="margin-top">12</property> + <property name="margin-bottom">12</property> <property name="orientation">vertical</property> <property name="spacing">6</property> <child> <object class="GtkLabel"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="halign">start</property> <property name="label" translatable="yes">Date</property> <style> @@ -100,16 +116,16 @@ <child> <object class="GtkBox"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="valign">start</property> <property name="orientation">vertical</property> <child> <object class="GtkCalendar" id="calendar"> - <property name="width_request">270</property> + <property name="width-request">270</property> <property name="visible">True</property> - <property name="can_focus">True</property> - <signal name="day-selected" handler="on_calendar_day_selected" swapped="no"/> - <signal name="month-changed" handler="on_calendar_month_changed" swapped="no"/> + <property name="can-focus">True</property> + <signal name="day-selected" handler="_on_day_selected" swapped="no"/> + <signal name="month-changed" handler="_on_month_changed" swapped="no"/> </object> <packing> <property name="expand">False</property> @@ -120,21 +136,21 @@ <child> <object class="GtkButtonBox"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="valign">start</property> <property name="homogeneous">True</property> - <property name="layout_style">expand</property> + <property name="layout-style">expand</property> <child> <object class="GtkButton" id="button_first_day"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> <signal name="clicked" handler="_change_date" swapped="no"/> <child> <object class="GtkImage"> <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="icon_name">go-first-symbolic</property> + <property name="can-focus">False</property> + <property name="icon-name">go-first-symbolic</property> </object> </child> </object> @@ -147,14 +163,14 @@ <child> <object class="GtkButton" id="button_previous_day"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> <signal name="clicked" handler="_change_date" swapped="no"/> <child> <object class="GtkImage"> <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="icon_name">go-previous-symbolic</property> + <property name="can-focus">False</property> + <property name="icon-name">go-previous-symbolic</property> </object> </child> </object> @@ -167,14 +183,14 @@ <child> <object class="GtkButton" id="button_next_day"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> <signal name="clicked" handler="_change_date" swapped="no"/> <child> <object class="GtkImage"> <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="icon_name">go-next-symbolic</property> + <property name="can-focus">False</property> + <property name="icon-name">go-next-symbolic</property> </object> </child> </object> @@ -187,14 +203,14 @@ <child> <object class="GtkButton" id="button_last_day"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> <signal name="clicked" handler="_change_date" swapped="no"/> <child> <object class="GtkImage"> <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="icon_name">go-last-symbolic</property> + <property name="can-focus">False</property> + <property name="icon-name">go-last-symbolic</property> </object> </child> </object> @@ -227,9 +243,9 @@ <child> <object class="GtkLabel"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="halign">start</property> - <property name="margin_top">12</property> + <property name="margin-top">12</property> <property name="label" translatable="yes">Mode</property> <style> <class name="dim-label"/> @@ -245,12 +261,12 @@ <object class="GtkRadioButton" id="search_complete_history"> <property name="label" translatable="yes">Search complete history</property> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">False</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> <property name="halign">start</property> <property name="active">True</property> - <property name="draw_indicator">True</property> - <signal name="toggled" handler="on_search_complete_history_toggled" swapped="no"/> + <property name="draw-indicator">True</property> + <signal name="toggled" handler="_on_search_complete_history" swapped="no"/> </object> <packing> <property name="expand">False</property> @@ -262,12 +278,12 @@ <object class="GtkRadioButton" id="search_in_date"> <property name="label" translatable="yes">Search selected day only</property> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">False</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> <property name="halign">start</property> - <property name="draw_indicator">True</property> + <property name="draw-indicator">True</property> <property name="group">search_complete_history</property> - <signal name="toggled" handler="on_search_in_date_toggled" swapped="no"/> + <signal name="toggled" handler="_on_search_in_date" swapped="no"/> </object> <packing> <property name="expand">False</property> @@ -279,25 +295,26 @@ </child> </object> <object class="GtkBox" id="history_box"> - <property name="width_request">600</property> + <property name="width-request">600</property> <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_start">18</property> - <property name="margin_end">18</property> - <property name="margin_top">18</property> - <property name="margin_bottom">18</property> + <property name="can-focus">False</property> + <property name="margin-start">18</property> + <property name="margin-end">18</property> + <property name="margin-top">18</property> + <property name="margin-bottom">18</property> <property name="orientation">vertical</property> <property name="spacing">24</property> <child> + <!-- n-columns=4 n-rows=1 --> <object class="GtkGrid"> <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="row_spacing">6</property> - <property name="column_spacing">12</property> + <property name="can-focus">False</property> + <property name="row-spacing">6</property> + <property name="column-spacing">12</property> <child> <object class="GtkLabel"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="halign">start</property> <property name="valign">center</property> <property name="label" translatable="yes">Chat</property> @@ -306,62 +323,62 @@ </style> </object> <packing> - <property name="left_attach">0</property> - <property name="top_attach">0</property> + <property name="left-attach">0</property> + <property name="top-attach">0</property> </packing> </child> <child> - <object class="GtkSwitch" id="log_history_checkbutton"> + <object class="GtkSwitch" id="store_history_switch"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="tooltip_text" translatable="yes">Store history for this chat</property> + <property name="can-focus">True</property> + <property name="tooltip-text" translatable="yes">Store history for this chat</property> <property name="valign">center</property> - <signal name="notify::active" handler="on_log_history_checkbutton_toggled" swapped="no"/> + <signal name="notify::active" handler="_on_log_history" swapped="no"/> </object> <packing> - <property name="left_attach">3</property> - <property name="top_attach">0</property> + <property name="left-attach">3</property> + <property name="top-attach">0</property> </packing> </child> <child> <object class="GtkLabel"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="valign">center</property> - <property name="margin_start">6</property> + <property name="margin-start">6</property> <property name="label" translatable="yes">Store History</property> <style> <class name="dim-label"/> </style> </object> <packing> - <property name="left_attach">2</property> - <property name="top_attach">0</property> + <property name="left-attach">2</property> + <property name="top-attach">0</property> </packing> </child> <child> - <object class="GtkComboBox" id="query_entry"> - <property name="width_request">400</property> + <object class="GtkComboBox" id="query_combo"> + <property name="width-request">400</property> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="valign">center</property> <property name="hexpand">True</property> <property name="model">liststore1</property> - <property name="has_entry">True</property> - <property name="entry_text_column">1</property> - <property name="id_column">1</property> - <signal name="changed" handler="on_jid_entry_changed" swapped="no"/> + <property name="has-entry">True</property> + <property name="entry-text-column">1</property> + <property name="id-column">1</property> + <signal name="changed" handler="_on_query_combo_changed" swapped="no"/> <child internal-child="entry"> - <object class="GtkEntry"> + <object class="GtkEntry" id="query_entry"> <property name="visible">True</property> - <property name="can_focus">True</property> - <signal name="activate" handler="on_jid_entry_activate" swapped="no"/> + <property name="can-focus">True</property> + <signal name="activate" handler="_on_jid_entry_activate" swapped="no"/> </object> </child> </object> <packing> - <property name="left_attach">1</property> - <property name="top_attach">0</property> + <property name="left-attach">1</property> + <property name="top-attach">0</property> </packing> </child> </object> @@ -374,25 +391,26 @@ <child> <object class="GtkBox"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="orientation">vertical</property> <child> <object class="GtkBox"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <child type="center"> <object class="GtkBox"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <child> <object class="GtkSearchEntry" id="search_entry"> - <property name="width_request">300</property> + <property name="width-request">300</property> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="primary_icon_name">edit-find-symbolic</property> - <property name="primary_icon_activatable">False</property> - <property name="primary_icon_sensitive">False</property> - <signal name="activate" handler="on_search_entry_activate" swapped="no"/> + <property name="can-focus">True</property> + <property name="primary-icon-name">edit-find-symbolic</property> + <property name="primary-icon-activatable">False</property> + <property name="primary-icon-sensitive">False</property> + <property name="placeholder-text" translatable="yes">Search…</property> + <signal name="activate" handler="_on_search_entry_activate" swapped="no"/> </object> <packing> <property name="expand">False</property> @@ -403,21 +421,21 @@ <child> <object class="GtkMenuButton" id="search_menu_button"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> <property name="halign">start</property> <property name="popover">search_menu</property> <child> <object class="GtkBox"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="spacing">6</property> <child> <object class="GtkLabel" id="date_label"> <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_start">3</property> - <property name="margin_end">3</property> + <property name="can-focus">False</property> + <property name="margin-start">3</property> + <property name="margin-end">3</property> </object> <packing> <property name="expand">False</property> @@ -428,8 +446,8 @@ <child> <object class="GtkImage"> <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="icon_name">pan-down-symbolic</property> + <property name="can-focus">False</property> + <property name="icon-name">pan-down-symbolic</property> </object> <packing> <property name="expand">False</property> @@ -459,19 +477,19 @@ <child> <object class="GtkBox"> <property name="visible">True</property> - <property name="can_focus">False</property> + <property name="can-focus">False</property> <property name="orientation">vertical</property> <child> <object class="GtkMenuButton"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> <property name="popover">more_menu</property> <child> <object class="GtkImage"> <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="icon_name">view-more-symbolic</property> + <property name="can-focus">False</property> + <property name="icon-name">view-more-symbolic</property> </object> </child> </object> @@ -485,7 +503,7 @@ <packing> <property name="expand">False</property> <property name="fill">True</property> - <property name="pack_type">end</property> + <property name="pack-type">end</property> <property name="position">1</property> </packing> </child> @@ -503,23 +521,70 @@ <child> <object class="GtkPaned"> <property name="visible">True</property> - <property name="can_focus">True</property> + <property name="can-focus">True</property> <property name="vexpand">True</property> <property name="orientation">vertical</property> <child> <object class="GtkScrolledWindow" id="results_scrolledwindow"> - <property name="can_focus">True</property> - <property name="no_show_all">True</property> - <property name="shadow_type">in</property> + <property name="can-focus">True</property> + <property name="no-show-all">True</property> + <property name="shadow-type">in</property> <child> <object class="GtkTreeView" id="results_treeview"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="hover_expand">True</property> - <signal name="cursor-changed" handler="on_results_treeview_cursor_changed" swapped="no"/> + <property name="can-focus">True</property> + <property name="model">search_liststore</property> + <property name="hover-expand">True</property> + <signal name="cursor-changed" handler="_on_results_cursor_changed" swapped="no"/> <child internal-child="selection"> <object class="GtkTreeSelection"/> </child> + <child> + <object class="GtkTreeViewColumn"> + <property name="resizable">True</property> + <property name="title" translatable="yes">Name</property> + <property name="clickable">True</property> + <property name="sort-column-id">1</property> + <child> + <object class="GtkCellRendererText" id="name"> + <property name="ellipsize">end</property> + </object> + <attributes> + <attribute name="text">1</attribute> + </attributes> + </child> + </object> + </child> + <child> + <object class="GtkTreeViewColumn"> + <property name="resizable">True</property> + <property name="title" translatable="yes">Date</property> + <property name="clickable">True</property> + <property name="sort-column-id">2</property> + <child> + <object class="GtkCellRendererText" id="date"> + <property name="ellipsize">end</property> + </object> + <attributes> + <attribute name="text">2</attribute> + </attributes> + </child> + </object> + </child> + <child> + <object class="GtkTreeViewColumn"> + <property name="resizable">True</property> + <property name="title" translatable="yes">Message</property> + <child> + <object class="GtkCellRendererText" id="message"> + <property name="ellipsize">end</property> + </object> + <attributes> + <attribute name="text">3</attribute> + </attributes> + </child> + </object> + </child> </object> </child> </object> @@ -531,9 +596,9 @@ <child> <object class="GtkScrolledWindow" id="scrolledwindow"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="shadow_type">etched-in</property> - <property name="min_content_height">200</property> + <property name="can-focus">True</property> + <property name="shadow-type">etched-in</property> + <property name="min-content-height">200</property> <child> <placeholder/> </child> diff --git a/gajim/data/style/gajim.css b/gajim/data/style/gajim.css index 9ff713181d81ab01e1dbfe63ed3700ba10c5ad00..a7b127f74dc012616274bcb4e9e000363d8b190c 100644 --- a/gajim/data/style/gajim.css +++ b/gajim/data/style/gajim.css @@ -59,6 +59,9 @@ .conversation-row grid textview text { .conversation-mention-highlight { background-color: rgb(255, 215, 194); } +.conversation-search-highlight { + background-color: rgb(194, 215, 255); +} .conversation-system-row { padding: 18px; } diff --git a/gajim/gtk/conversation/rows/base.py b/gajim/gtk/conversation/rows/base.py index f809f928a45a43350c1217a592395dd23641041e..96c3b4576f367dc891012526bef3cfd9f1d8915e 100644 --- a/gajim/gtk/conversation/rows/base.py +++ b/gajim/gtk/conversation/rows/base.py @@ -37,6 +37,7 @@ def __init__(self, account, widget='label', history_mode=False): self.kind = None self.name = None self.message_id = None + self.log_line_id = None self.text = '' self.get_style_context().add_class('conversation-row') @@ -98,8 +99,9 @@ def create_name_widget(name: str, kind: str, @wrap_with_event_box class MoreMenuButton(Gtk.MenuButton): - def __init__(self, row): + def __init__(self, row, history_mode=False): Gtk.MenuButton.__init__(self) + self._history_mode = history_mode self.set_valign(Gtk.Align.START) self.set_halign(Gtk.Align.END) @@ -116,17 +118,18 @@ def _create_popover(self, row): menu_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) menu_box.get_style_context().add_class('padding-6') - quote_button = Gtk.ModelButton() - quote_button.set_halign(Gtk.Align.START) - quote_button.connect('clicked', row._on_quote_message) - quote_button.set_label(_('Quote…')) - quote_button.set_image(Gtk.Image.new_from_icon_name( - 'mail-reply-sender-symbolic', Gtk.IconSize.MENU)) - menu_box.add(quote_button) + if not self._history_mode: + quote_button = Gtk.ModelButton() + quote_button.set_halign(Gtk.Align.START) + quote_button.connect('clicked', row.on_quote_message) + quote_button.set_label(_('Quote…')) + quote_button.set_image(Gtk.Image.new_from_icon_name( + 'mail-reply-sender-symbolic', Gtk.IconSize.MENU)) + menu_box.add(quote_button) copy_button = Gtk.ModelButton() copy_button.set_halign(Gtk.Align.START) - copy_button.connect('clicked', row._on_copy_message) + copy_button.connect('clicked', row.on_copy_message) copy_button.set_label(_('Copy')) copy_button.set_image(Gtk.Image.new_from_icon_name( 'edit-copy-symbolic', Gtk.IconSize.MENU)) diff --git a/gajim/gtk/conversation/rows/message.py b/gajim/gtk/conversation/rows/message.py index 8f19bd1de7b7392a63cc67c9377955a011cd4528..84363293aa71673950ad0855eabf930f89d16353 100644 --- a/gajim/gtk/conversation/rows/message.py +++ b/gajim/gtk/conversation/rows/message.py @@ -45,7 +45,8 @@ def __init__(self, marker=None, error=None, encryption_enabled=False, - history_mode=False): + history_mode=False, + log_line_id=None): # other_tags_for_name contained 'marked', 'bold' and # 'muc_nickname_color_', which are now derived from @@ -58,6 +59,7 @@ def __init__(self, self.type = 'chat' self.timestamp = timestamp self.message_id = message_id + self.log_line_id = log_line_id self.kind = kind self.name = name or '' self.text = text @@ -124,19 +126,19 @@ def __init__(self, bottom_box = Gtk.Box(spacing=6) bottom_box.add(self.textview) - bottom_box.add(MoreMenuButton(self)) + bottom_box.add(MoreMenuButton(self, history_mode=history_mode)) self.grid.attach(avatar_placeholder, 0, 0, 1, 2) self.grid.attach(self._meta_box, 1, 0, 1, 1) self.grid.attach(bottom_box, 1, 1, 1, 1) - def _on_copy_message(self, _widget): + def on_copy_message(self, _widget): timestamp = self.timestamp.strftime('%x, %X') clip = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) clip.set_text( f'{timestamp} - {self.name}: {self.textview.get_text()}', -1) - def _on_quote_message(self, _widget): + def on_quote_message(self, _widget): self.get_parent().on_quote(self.textview.get_text()) def _get_encryption_image(self, additional_data, encryption_enabled=None): diff --git a/gajim/gtk/conversation/rows/scroll_hint.py b/gajim/gtk/conversation/rows/scroll_hint.py index e75422a31ed83450c4611d296727592d4303b2d7..919e730ecbb573bc667b7588e116994de0e7d9c2 100644 --- a/gajim/gtk/conversation/rows/scroll_hint.py +++ b/gajim/gtk/conversation/rows/scroll_hint.py @@ -22,24 +22,31 @@ class ScrollHintRow(BaseRow): - def __init__(self, account): + def __init__(self, account, history_mode=False): BaseRow.__init__(self, account) self.type = 'system' self.timestamp = datetime.fromtimestamp(0) - self.get_style_context().add_class('conversation-system-row') - self._button = Gtk.Button.new_from_icon_name( - 'go-up-symbolic', Gtk.IconSize.BUTTON) - self._button.set_tooltip_text(_('Load more messages')) - self._button.connect('clicked', self._on_load_history) - self.grid.attach(self._button, 0, 0, 1, 1) + self.get_style_context().add_class('conversation-system-row') - self.label.set_text(_('Scroll up to load more chat history…')) self.label.set_halign(Gtk.Align.CENTER) self.label.set_hexpand(True) self.label.get_style_context().add_class( 'conversation-meta') + + if history_mode: + self.label.set_text(_('Use the calendar to select a specific date')) + self.grid.attach(self.label, 0, 1, 1, 1) + return + + self.label.set_text(_('Scroll up to load more chat history…')) self.grid.attach(self.label, 0, 1, 1, 1) + self._button = Gtk.Button.new_from_icon_name( + 'go-up-symbolic', Gtk.IconSize.BUTTON) + self._button.set_tooltip_text(_('Load more messages')) + self._button.connect('clicked', self._on_load_history) + self.grid.attach(self._button, 0, 0, 1, 1) + def _on_load_history(self, _button): self.get_parent().emit('load-history', 30) diff --git a/gajim/gtk/conversation/view.py b/gajim/gtk/conversation/view.py index ffff9ddbbb9c12f338140582afcef671dcb61abc..a20a3caf27c5f7ca7fb2507f0bcd400e66544c78 100644 --- a/gajim/gtk/conversation/view.py +++ b/gajim/gtk/conversation/view.py @@ -95,7 +95,7 @@ def __init__(self, account, contact, history_mode=False): self._last_incoming_timestamp = datetime.fromtimestamp(0) # Insert the very first row, containing the scroll hint and load button - self.add(ScrollHintRow(self._account)) + self.add(ScrollHintRow(self._account, history_mode=self._history_mode)) self._timestamps_inserted.append(datetime.fromtimestamp(0)) def clear(self): @@ -121,6 +121,7 @@ def add_message(self, name, timestamp, other_text_tags=None, + log_line_id=None, message_id=None, correct_id=None, display_marking=None, @@ -153,7 +154,8 @@ def add_message(self, if other_text_tags is None: other_text_tags = [] - if kind in ('status', 'info') or subject: + if (kind in ('status', 'info') or + (subject and self._contact.is_groupchat)): message = InfoMessageRow( self._account, time_, @@ -191,7 +193,8 @@ def add_message(self, marker=marker, error=error, encryption_enabled=self.encryption_enabled, - history_mode=self._history_mode) + history_mode=self._history_mode, + log_line_id=log_line_id) self._insert_message(message, time_, kind, history) @@ -382,12 +385,22 @@ def _reduce_message_count(self): self.first_message_timestamp = None self._row_count -= 1 - def _get_row_by_id(self, id_): + def _get_row_by_message_id(self, id_): for row in self.get_children(): if row.message_id == id_: return row return None + def get_row_by_log_line_id(self, log_line_id): + for row in self.get_children(): + if row.log_line_id == log_line_id: + return row + return None + + def iter_rows(self): + for row in self.get_children(): + yield row + def _get_first_chat_row(self): for row in self.get_children(): if row.type == 'chat': @@ -395,7 +408,7 @@ def _get_first_chat_row(self): return None def set_read_marker(self, id_): - message_row = self._get_row_by_id(id_) + message_row = self._get_row_by_message_id(id_) if message_row is None: return @@ -476,7 +489,7 @@ def scroll_to_end(self, force=False): def correct_message(self, correct_id, message_id, text, other_text_tags, kind, name, additional_data=None): - message_row = self._get_row_by_id(correct_id) + message_row = self._get_row_by_message_id(correct_id) if message_row is not None: message_row.set_correction( message_id, text, other_text_tags, kind, name, @@ -484,12 +497,12 @@ def correct_message(self, correct_id, message_id, text, message_row.set_merged(False) def show_receipt(self, id_): - message_row = self._get_row_by_id(id_) + message_row = self._get_row_by_message_id(id_) if message_row is not None: message_row.set_receipt() def show_error(self, id_, error): - message_row = self._get_row_by_id(id_) + message_row = self._get_row_by_message_id(id_) if message_row is not None: message_row.set_error(to_user_string(error)) message_row.set_merged(False) diff --git a/gajim/gtk/history.py b/gajim/gtk/history.py index 888320a0ccc89d7d6e4740d7beecb10e20a3596f..c75fea89cccc778dc33d437a2fb0063cdb1f20f4 100644 --- a/gajim/gtk/history.py +++ b/gajim/gtk/history.py @@ -32,14 +32,13 @@ from gajim.common import app from gajim.common import helpers -from gajim.common import exceptions from gajim.common.i18n import _ -from gajim.common.const import ShowConstant from gajim.common.const import KindConstant -from gajim.common.const import StyleAttr +from gajim.common.exceptions import PysqliteOperationalError -from gajim import conversation_textview +# from gajim import conversation_textview +from .conversation.view import ConversationView from .util import python_month from .util import gtk_month from .util import resize_window @@ -51,6 +50,7 @@ from .dialogs import ErrorDialog + @unique class InfoColumn(IntEnum): '''Completion dict''' @@ -71,68 +71,44 @@ class Column(IntEnum): class HistoryWindow(Gtk.ApplicationWindow): - def __init__(self, jid=None, account=None): + def __init__(self, account=None, jid=None): Gtk.ApplicationWindow.__init__(self) self.set_application(app.app) self.set_position(Gtk.WindowPosition.CENTER) self.set_show_menubar(False) self.set_title(_('Conversation History')) - self._ui = get_builder('history_window.ui') + self.account = account + self.jid = jid + self._client = app.get_client(account) + self._contact = None + if jid is not None: + self._contact = self._client.get_module('Contacts').get_contact( + jid) + + self._ui = get_builder('history_window.ui') self.add(self._ui.history_box) - self.history_textview = conversation_textview.ConversationTextview( - account, used_in_history_window=True) - self._ui.scrolledwindow.add(self.history_textview.tv) - self.history_buffer = self.history_textview.tv.get_buffer() - highlight_color = app.css_config.get_value( - '.gajim-search-highlight', StyleAttr.COLOR) - self.history_buffer.create_tag('highlight', background=highlight_color) - self.history_buffer.create_tag('invisible', invisible=True) - - self.clearing_search = False - - # jid, contact_name, date, message, time, log_line_id - model = Gtk.ListStore(str, str, str, str, str, int) - self._ui.results_treeview.set_model(model) - col = Gtk.TreeViewColumn(_('Name')) - self._ui.results_treeview.append_column(col) - renderer = Gtk.CellRendererText() - col.pack_start(renderer, True) - col.add_attribute(renderer, 'text', Column.CONTACT_NAME) - # user can click this header and sort - col.set_sort_column_id(Column.CONTACT_NAME) - col.set_resizable(True) - - col = Gtk.TreeViewColumn(_('Date')) - self._ui.results_treeview.append_column(col) - renderer = Gtk.CellRendererText() - col.pack_start(renderer, True) - col.add_attribute(renderer, 'text', Column.UNIXTIME) - # user can click this header and sort - col.set_sort_column_id(Column.UNIXTIME) - col.set_resizable(True) - - col = Gtk.TreeViewColumn(_('Message')) - self._ui.results_treeview.append_column(col) - renderer = Gtk.CellRendererText() - col.pack_start(renderer, True) - col.add_attribute(renderer, 'text', Column.MESSAGE) - col.set_resizable(True) - - self.jid = None # The history we are currently viewing - self.account = account - self.completion_dict = {} - self.accounts_seen_online = [] # Update dict when new accounts connect - self.jids_to_search = [] + self._conversation_view = ConversationView( + account, self._contact, history_mode=True) + self._ui.scrolledwindow.add(self._conversation_view) + self._ui.scrolledwindow.set_focus_vadjustment(Gtk.Adjustment()) + + self._clearing_search = False + self._first_day = None + self._last_day = None + + self._completion_dict = {} + self._accounts_seen_online = [] # Update dict when new accounts connect + self._jids_to_search = [] # This will load history too task = self._fill_completion_dict() GLib.idle_add(next, task) if jid: - self._ui.query_entry.get_child().set_text(jid) + self._ui.query_entry.set_text(jid) else: self._load_history(None) @@ -154,6 +130,38 @@ def __init__(self, jid=None, account=None): app.plugin_manager.gui_extension_point( 'history_window', self) + def _on_delete(self, _widget, *args): + self._save_state() + + def _on_destroy(self, _widget): + app.plugin_manager.remove_gui_extension_point( + 'history_window', self) + + def _on_key_press(self, _widget, event): + if event.keyval == Gdk.KEY_Escape: + if self._ui.results_scrolledwindow.get_visible(): + self._ui.results_scrolledwindow.set_visible(False) + return + self._save_state() + self.destroy() + + def _on_jid_entry_match_selected(self, _widget, model, iter_, *args): + self._jid_entry_search(model[iter_][1]) + return True + + def _on_query_combo_changed(self, combo): + # only if selected from combobox + jid = self._ui.query_entry.get_text() + if jid == combo.get_active_id(): + self._jid_entry_search(jid) + + def _on_jid_entry_activate(self, entry): + self._jid_entry_search(entry.get_text()) + + def _jid_entry_search(self, jid): + self._load_history(jid, self.account) + self._ui.results_scrolledwindow.set_visible(False) + def _fill_completion_dict(self): """ Fill completion_dict for key auto completion. Then load history for @@ -165,23 +173,22 @@ def _fill_completion_dict(self): {key : (jid, account, nick_name, full_completion_name} This is a generator and does pseudo-threading via idle_add(). """ - liststore = get_completion_liststore( - self._ui.query_entry.get_child()) + liststore = get_completion_liststore(self._ui.query_entry) liststore.set_sort_column_id(1, Gtk.SortType.ASCENDING) - self._ui.query_entry.get_child().get_completion().connect( - 'match-selected', self.on_jid_entry_match_selected) + self._ui.query_entry.get_completion().connect( + 'match-selected', self._on_jid_entry_match_selected) - self._ui.query_entry.set_model(liststore) + self._ui.query_combo.set_model(liststore) # Add all jids in logs.db: db_jids = app.storage.archive.get_jids_in_db() completion_dict = dict.fromkeys(db_jids) - self.accounts_seen_online = list(app.settings.get_active_accounts()) + self._accounts_seen_online = list(app.settings.get_active_accounts()) # Enhance contacts of online accounts with contact. # Needed for mapping below - for account in self.accounts_seen_online: + for account in self._accounts_seen_online: completion_dict.update( helpers.get_contact_dict_for_account(account)) @@ -190,7 +197,7 @@ def _fill_completion_dict(self): keys = list(completion_dict.keys()) # Move the actual jid at first so we load history faster - actual_jid = self._ui.query_entry.get_child().get_text() + actual_jid = self._ui.query_entry.get_text() if actual_jid in keys: keys.remove(actual_jid) keys.insert(0, actual_jid) @@ -205,7 +212,7 @@ def _fill_completion_dict(self): completed2 = None contact = completion_dict[completed] if contact: - info_name = contact.get_shown_name() + info_name = contact.name info_completion = info_name info_jid = contact.jid else: @@ -233,15 +240,15 @@ def _fill_completion_dict(self): if len(completed) > 70: completed = completed[:70] + '[\u2026]' liststore.append((icon, completed)) - self.completion_dict[key] = ( + self._completion_dict[key] = ( info_jid, info_acc, info_name, info_completion) - self.completion_dict[completed] = ( + self._completion_dict[completed] = ( info_jid, info_acc, info_name, info_completion) if completed2: if len(completed2) > 70: completed2 = completed2[:70] + '[\u2026]' liststore.append((icon, completed2)) - self.completion_dict[completed2] = ( + self._completion_dict[completed2] = ( info_jid, info_acc, info_name, info_completion2) if key == actual_jid: self._load_history(info_jid, self.account or info_acc) @@ -249,7 +256,8 @@ def _fill_completion_dict(self): keys.sort() yield False - def _get_account_for_jid(self, jid): + @staticmethod + def _get_account_for_jid(jid): """ Return the corresponding account of the jid. May be None if an account could not be found @@ -264,49 +272,14 @@ def _get_account_for_jid(self, jid): break return account - def _on_delete(self, widget, *args): - self.save_state() - - def _on_destroy(self, widget): - # PluginSystem: removing GUI extension points connected with - # HistoryWindow instance object - app.plugin_manager.remove_gui_extension_point( - 'history_window', self) - self.history_textview.del_handlers() - - def _on_key_press(self, widget, event): - if event.keyval == Gdk.KEY_Escape: - if self._ui.results_scrolledwindow.get_visible(): - self._ui.results_scrolledwindow.set_visible(False) - return - self.save_state() - self.destroy() - - def on_jid_entry_match_selected(self, widget, model, iter_, *args): - self._jid_entry_search(model[iter_][1]) - return True - - def on_jid_entry_changed(self, widget): - # only if selected from combobox - jid = self._ui.query_entry.get_child().get_text() - if jid == self._ui.query_entry.get_active_id(): - self._jid_entry_search(jid) - - def on_jid_entry_activate(self, widget): - self._jid_entry_search(self._ui.query_entry.get_child().get_text()) - - def _jid_entry_search(self, jid): - self._load_history(jid, self.account) - self._ui.results_scrolledwindow.set_visible(False) - def _load_history(self, jid_or_name, account=None): """ Load history for the given jid/name and show it """ - if jid_or_name and jid_or_name in self.completion_dict: + if jid_or_name and jid_or_name in self._completion_dict: # a full qualified jid or a contact name was entered - info_jid, info_account, _info_name, info_completion = self.completion_dict[jid_or_name] - self.jids_to_search = [info_jid] + info_jid, info_account, _info_name, info_completion = self._completion_dict[jid_or_name] + self._jids_to_search = [info_jid] self.jid = info_jid if account: @@ -317,37 +290,35 @@ def _load_history(self, jid_or_name, account=None): # We don't know account. Probably a gc not opened or an # account not connected. # Disable possibility to say if we want to log or not - self._ui.log_history_checkbutton.set_sensitive(False) + self._ui.store_history_switch.set_sensitive(False) else: - # Are log disabled for account ? - if self.account in app.settings.get_account_setting( - self.account, 'no_log_for').split(' '): - self._ui.log_history_checkbutton.set_active(False) - self._ui.log_history_checkbutton.set_sensitive(False) + # Are logs disabled for account ? + no_log_for = app.settings.get_account_setting( + self.account, 'no_log_for').split(' ') + if self.account in no_log_for: + self._ui.store_history_switch.set_active(False) + self._ui.store_history_switch.set_sensitive(False) else: - # Are log disabled for jid ? - log = True - if self.jid in app.settings.get_account_setting( - self.account, 'no_log_for').split(' '): - log = False - self._ui.log_history_checkbutton.set_active(log) - self._ui.log_history_checkbutton.set_sensitive(True) + # Are logs disabled for jid ? + self._ui.store_history_switch.set_active( + str(self.jid) not in no_log_for) + self._ui.store_history_switch.set_sensitive(True) - self.jids_to_search = [info_jid] + self._jids_to_search = [info_jid] # Get first/last date we have logs with contact self.first_log = app.storage.archive.get_first_date_that_has_logs( self.account, self.jid) - self.first_day = self._get_date_from_timestamp(self.first_log) + self._first_day = self._get_date_from_timestamp(self.first_log) self.last_log = app.storage.archive.get_last_date_that_has_logs( self.account, self.jid) - self.last_day = self._get_date_from_timestamp(self.last_log) + self._last_day = self._get_date_from_timestamp(self.last_log) # Select logs for last date we have logs with contact self._ui.search_menu_button.set_sensitive(True) - month = gtk_month(self.last_day.month) - self._ui.calendar.select_month(month, self.last_day.year) - self._ui.calendar.select_day(self.last_day.day) + month = gtk_month(self._last_day.month) + self._ui.calendar.select_month(month, self._last_day.year) + self._ui.calendar.select_day(self._last_day.day) self._ui.button_previous_day.set_sensitive(True) self._ui.button_next_day.set_sensitive(True) @@ -357,7 +328,7 @@ def _load_history(self, jid_or_name, account=None): self._ui.search_entry.set_sensitive(True) self._ui.search_entry.grab_focus() - self._ui.query_entry.get_child().set_text(info_completion) + self._ui.query_entry.set_text(info_completion) else: # neither a valid jid, nor an existing contact name was entered @@ -365,10 +336,8 @@ def _load_history(self, jid_or_name, account=None): self.jid = None self.account = None - self.history_buffer.set_text('') # clear the buffer self._ui.search_entry.set_sensitive(False) - - self._ui.log_history_checkbutton.set_sensitive(False) + self._ui.store_history_switch.set_sensitive(False) self._ui.search_menu_button.set_sensitive(False) self._ui.calendar.clear_marks() self._ui.button_previous_day.set_sensitive(False) @@ -378,7 +347,7 @@ def _load_history(self, jid_or_name, account=None): self._ui.results_scrolledwindow.set_visible(False) - def on_calendar_day_selected(self, widget): + def _on_day_selected(self, *args): if not self.jid: return year, month, day = self._ui.calendar.get_date() # integers @@ -388,102 +357,86 @@ def on_calendar_day_selected(self, widget): self._load_conversation(year, month, day) GLib.idle_add(scroll_to_end, self._ui.scrolledwindow) - def on_calendar_month_changed(self, widget): + def _on_month_changed(self, calendar): """ Ask for days in this month, if they have logs it bolds them (marks them) """ if not self.jid: return - year, month, _day = widget.get_date() # integers - if year < 1900: - widget.select_month(0, 1900) - widget.select_day(1) + year, month, _day = calendar.get_date() # integers + if year < 2000: + calendar.select_month(0, 2000) + calendar.select_day(1) return - widget.clear_marks() + calendar.clear_marks() month = python_month(month) try: log_days = app.storage.archive.get_days_with_logs( self.account, self.jid, year, month) - except exceptions.PysqliteOperationalError as error: - ErrorDialog(_('Disk Error'), str(error)) + except PysqliteOperationalError as err: + ErrorDialog(_('Disk Error'), str(err)) return for date in log_days: - widget.mark_day(date.day) + calendar.mark_day(date.day) - def _get_date_from_timestamp(self, timestamp): + @staticmethod + def _get_date_from_timestamp(timestamp): # Conversion from timestamp to date log = time.localtime(timestamp) - y, m, d = log[0], log[1], log[2] - date = datetime.datetime(y, m, d) + year, mmonth, day = log[0], log[1], log[2] + date = datetime.datetime(year, mmonth, day) return date - def _change_date(self, widget): - # Get day selected in calendar - y, m, d = self._ui.calendar.get_date() - py_m = python_month(m) - _date = datetime.datetime(y, py_m, d) + def _change_date(self, button): + year, month, day = self._ui.calendar.get_date() + python_m = python_month(month) + date_ = datetime.datetime(year, python_m, day) - if widget is self._ui.button_first_day: - gtk_m = gtk_month(self.first_day.month) - self._ui.calendar.select_month(gtk_m, self.first_day.year) - self._ui.calendar.select_day(self.first_day.day) + if button is self._ui.button_first_day: + gtk_m = gtk_month(self._first_day.month) + self._ui.calendar.select_month(gtk_m, self._first_day.year) + self._ui.calendar.select_day(self._first_day.day) return - if widget is self._ui.button_last_day: - gtk_m = gtk_month( - self.last_day.month) - self._ui.calendar.select_month(gtk_m, self.last_day.year) - self._ui.calendar.select_day(self.last_day.day) + if button is self._ui.button_last_day: + gtk_m = gtk_month(self._last_day.month) + self._ui.calendar.select_month(gtk_m, self._last_day.year) + self._ui.calendar.select_day(self._last_day.day) return - if widget is self._ui.button_previous_day: - end_date = self.first_day + if button is self._ui.button_previous_day: + end_date = self._first_day timedelta = datetime.timedelta(days=-1) - if end_date >= _date: + if end_date >= date_: return - elif widget is self._ui.button_next_day: - end_date = self.last_day + + if button is self._ui.button_next_day: + end_date = self._last_day timedelta = datetime.timedelta(days=1) - if end_date <= _date: + if end_date <= date_: return # Iterate through days until log entry found or # supplied end_date (first_log / last_log) reached logs = None while logs is None: - _date = _date + timedelta - if _date == end_date: + date_ = date_ + timedelta + if date_ == end_date: break try: logs = app.storage.archive.get_date_has_logs( - self.account, self.jid, _date) - except exceptions.PysqliteOperationalError as e: - ErrorDialog(_('Disk Error'), str(e)) + self.account, self.jid, date_) + except PysqliteOperationalError as err: + ErrorDialog(_('Disk Error'), str(err)) return - gtk_m = gtk_month(_date.month) - self._ui.calendar.select_month(gtk_m, _date.year) - self._ui.calendar.select_day(_date.day) - - def _get_string_show_from_constant_int(self, show): - if show == ShowConstant.ONLINE: - show = 'online' - elif show == ShowConstant.CHAT: - show = 'chat' - elif show == ShowConstant.AWAY: - show = 'away' - elif show == ShowConstant.XA: - show = 'xa' - elif show == ShowConstant.DND: - show = 'dnd' - elif show == ShowConstant.OFFLINE: - show = 'offline' - - return show + gtk_m = gtk_month(date_.month) + self._ui.calendar.select_month(gtk_m, date_.year) + self._ui.calendar.select_day(date_.day) def _load_conversation(self, year, month, day): """ @@ -491,162 +444,57 @@ def _load_conversation(self, year, month, day): given date into the history textbuffer. Values for `month` and `day` are 1-based. """ - self.history_buffer.set_text('') - self.last_time_printout = 0 - show_status = self._ui.show_status_checkbutton.get_active() + self._conversation_view.clear() + show_status = self._ui.show_status_checkbutton.get_active() date = datetime.datetime(year, month, day) - conversation = app.storage.archive.get_conversation_for_date( + messages = app.storage.archive.get_messages_for_date( self.account, self.jid, date) - - for message in conversation: - if not show_status and message.kind in (KindConstant.GCSTATUS, - KindConstant.STATUS): - continue - self._add_message(message) - - def _add_message(self, msg): - if not msg.message and msg.kind not in (KindConstant.STATUS, + for msg in messages: + if not show_status and msg.kind in (KindConstant.STATUS, KindConstant.GCSTATUS): - return + continue + if not msg.message and msg.kind not in (KindConstant.STATUS, + KindConstant.GCSTATUS): + continue - tim = msg.time - kind = msg.kind - show = msg.show - message = msg.message - subject = msg.subject - log_line_id = msg.log_line_id - contact_name = msg.contact_name - additional_data = msg.additional_data - - buf = self.history_buffer - end_iter = buf.get_end_iter() - - # Make the beginning of every message searchable by its log_line_id - buf.create_mark(str(log_line_id), end_iter, left_gravity=True) - - if app.settings.get('print_time') == 'always': - timestamp_str = app.settings.get('time_stamp') - timestamp_str = helpers.from_one_line(timestamp_str) - tim = time.strftime(timestamp_str, time.localtime(float(tim))) - buf.insert(end_iter, tim) - elif app.settings.get('print_time') == 'sometimes': - every_foo_seconds = 60 * app.settings.get( - 'print_ichat_every_foo_minutes') - seconds_passed = tim - self.last_time_printout - if seconds_passed > every_foo_seconds: - self.last_time_printout = tim - tim = time.strftime('%X ', time.localtime(float(tim))) - buf.insert_with_tags_by_name( - end_iter, tim + '\n', 'time_sometimes') - - # print the encryption icon - if kind in (KindConstant.CHAT_MSG_SENT, - KindConstant.CHAT_MSG_RECV): - self.history_textview.print_encryption_status( - end_iter, additional_data) - - tag_name = '' - tag_msg = '' - - show = self._get_string_show_from_constant_int(show) - - if kind == KindConstant.GC_MSG: - tag_name = 'incoming' - elif kind in (KindConstant.SINGLE_MSG_RECV, - KindConstant.CHAT_MSG_RECV): - contact_name = self.completion_dict[self.jid][InfoColumn.NAME] - tag_name = 'incoming' - tag_msg = 'incomingtxt' - elif kind in (KindConstant.SINGLE_MSG_SENT, - KindConstant.CHAT_MSG_SENT): - if self.account: + kind = 'status' + contact_name = msg.contact_name + if msg.kind in ( + KindConstant.SINGLE_MSG_RECV, KindConstant.CHAT_MSG_RECV): + kind = 'incoming' + contact_name = self._contact.name + elif msg.kind == KindConstant.GC_MSG: + kind = 'incoming' + elif msg.kind in ( + KindConstant.SINGLE_MSG_SENT, KindConstant.CHAT_MSG_SENT): + kind = 'outgoing' contact_name = app.nicks[self.account] - else: - # we don't have roster, we don't know our own nick, use first - # account one (urk!) - account = list(app.settings.get_active_accounts())[0] - contact_name = app.nicks[account] - tag_name = 'outgoing' - tag_msg = 'outgoingtxt' - elif kind == KindConstant.GCSTATUS: - # message here (if not None) is status message - if message: - message = _('%(nick)s is now %(status)s: %(status_msg)s') % { - 'nick': contact_name, - 'status': helpers.get_uf_show(show), - 'status_msg': message} - else: - message = _('%(nick)s is now %(status)s') % { - 'nick': contact_name, - 'status': helpers.get_uf_show(show)} - tag_msg = 'status' - else: # 'status' - # message here (if not None) is status message - if show is None: # it means error - if message: - message = _('Error: %s') % message - else: - message = _('Error') - elif message: - message = _('Status is now: %(status)s: %(status_msg)s') % { - 'status': helpers.get_uf_show(show), - 'status_msg': message} - else: - message = _('Status is now: %(status)s') % { - 'status': helpers.get_uf_show(show)} - tag_msg = 'status' - if message.startswith('/me ') or message.startswith('/me\n'): - tag_msg = tag_name - else: - # do not do this if gcstats, avoid dupping contact_name - # eg. nkour: nkour is now Offline - if contact_name and kind != KindConstant.GCSTATUS: - # add stuff before and after contact name - before_str = app.settings.get('before_nickname') - before_str = helpers.from_one_line(before_str) - after_str = app.settings.get('after_nickname') - after_str = helpers.from_one_line(after_str) - format_ = before_str + contact_name + after_str + ' ' - if tag_name: - buf.insert_with_tags_by_name(end_iter, format_, tag_name) - else: - buf.insert(end_iter, format_) - if subject: - message = _('Subject: %s\n') % subject + message - - if tag_msg: - self.history_textview.print_real_text( - message, - [tag_msg], - name=contact_name, - additional_data=additional_data) - else: - self.history_textview.print_real_text( - message, - name=contact_name, - additional_data=additional_data) - self.history_textview.print_real_text('\n', text_tags=['eol']) - - def on_search_complete_history_toggled(self, widget): + self._conversation_view.add_message( + msg.message, + kind, + contact_name, + msg.time, + subject=msg.subject, + additional_data=msg.additional_data, + history=True, + log_line_id=msg.log_line_id) + + def _on_search_complete_history(self, _widget): self._ui.date_label.get_style_context().remove_class('tagged') - def on_search_in_date_toggled(self, widget): + def _on_search_in_date(self, _widget): self._ui.date_label.get_style_context().add_class('tagged') - def on_search_entry_activate(self, widget): - text = self._ui.search_entry.get_text() + def _on_search_entry_activate(self, entry): + text = entry.get_text() model = self._ui.results_treeview.get_model() - self.clearing_search = True + self._clearing_search = True model.clear() - self.clearing_search = False - - start = self.history_buffer.get_start_iter() - end = self.history_buffer.get_end_iter() - self.history_buffer.remove_tag_by_name('highlight', start, end) + self._clearing_search = False if text == '': self._ui.results_scrolledwindow.set_visible(False) @@ -655,9 +503,9 @@ def on_search_entry_activate(self, widget): self._ui.results_scrolledwindow.set_visible(True) # perform search in preselected jids - # jids are preselected with the query_entry - for jid in self.jids_to_search: - account = self.completion_dict[jid][InfoColumn.ACCOUNT] + # jids are preselected with the query_combo + for jid in self._jids_to_search: + account = self._completion_dict[jid][InfoColumn.ACCOUNT] if account is None: # We do not know an account. This can only happen if # the contact is offine, or if we browse a groupchat history. @@ -688,30 +536,30 @@ def on_search_entry_activate(self, widget): if row.kind == KindConstant.CHAT_MSG_SENT: contact_name = app.nicks[account] else: - contact_name = self.completion_dict[jid][InfoColumn.NAME] + contact_name = self._completion_dict[jid][InfoColumn.NAME] local_time = time.localtime(row.time) date = time.strftime('%Y-%m-%d', local_time) result_found = True - model.append((jid, contact_name, date, row.message, - str(row.time), row.log_line_id)) + model.append((str(jid), contact_name, date, row.message, + str(row.time), str(row.log_line_id))) if result_found: self._ui.results_treeview.set_cursor(0) - def on_results_treeview_cursor_changed(self, *args): + def _on_results_cursor_changed(self, treeview): """ A row was selected, get date from row, and select it in calendar which results to showing conversation logs for that date """ - if self.clearing_search: + if self._clearing_search: return # get currently selected date cur_year, cur_month, cur_day = self._ui.calendar.get_date() cur_month = python_month(cur_month) - model, paths = self._ui.results_treeview.get_selection().get_selected_rows() + model, paths = treeview.get_selection().get_selected_rows() if not paths: return @@ -739,77 +587,41 @@ def on_results_treeview_cursor_changed(self, *args): self._scroll_to_message_and_highlight(model[path][Column.LOG_LINE_ID]) def _scroll_to_message_and_highlight(self, log_line_id): - """ - Scroll to a message and highlight it - """ - - def iterator_has_mark(iterator, mark_name): - for mark in iterator.get_marks(): - if mark.get_name() == mark_name: - return True - return False - - # Clear previous search result by removing the highlighting. The scroll - # mark is automatically removed when the new one is set. - start = self.history_buffer.get_start_iter() - end = self.history_buffer.get_end_iter() - self.history_buffer.remove_tag_by_name('highlight', start, end) - - log_line_id = str(log_line_id) - line = start - while not iterator_has_mark(line, log_line_id): - if not line.forward_line(): - return - - match_start = line - match_end = match_start.copy() - match_end.forward_to_tag_toggle(self.history_buffer.eol_tag) - - self.history_buffer.apply_tag_by_name( - 'highlight', match_start, match_end) - mark = self.history_buffer.create_mark('match', match_start, True) - GLib.idle_add( - self.history_textview.tv.scroll_to_mark, mark, 0, True, 0.0, 0.5) - - def on_log_history_checkbutton_toggled(self, widget, *args): - # log conversation history? + for row in self._conversation_view.iter_rows(): + row.get_style_context().remove_class( + 'conversation-search-highlight') + + row = self._conversation_view.get_row_by_log_line_id(int(log_line_id)) + if row is not None: + row.get_style_context().add_class('conversation-search-highlight') + # This scrolls the ListBox to the highlighted row + row.grab_focus() + self._ui.results_treeview.grab_focus() + + def _on_log_history(self, switch, *args): + jid = str(self.jid) oldlog = True no_log_for = app.settings.get_account_setting( self.account, 'no_log_for').split() - if self.jid in no_log_for: + if jid in no_log_for: oldlog = False - log = widget.get_active() - if not log and self.jid not in no_log_for: - no_log_for.append(self.jid) - if log and self.jid in no_log_for: - no_log_for.remove(self.jid) + log = switch.get_active() + if not log and jid not in no_log_for: + no_log_for.append(jid) + if log and jid in no_log_for: + no_log_for.remove(jid) if oldlog != log: app.settings.set_account_setting( self.account, 'no_log_for', ' '.join(no_log_for)) - def on_show_status_checkbutton_toggled(self, widget): - # reload logs - self.on_calendar_day_selected(None) - - def open_history(self, jid, account): - """ - Load chat history of the specified jid - """ - self._ui.query_entry.get_child().set_text(jid) - if account and account not in self.accounts_seen_online: - # Update dict to not only show bare jid - GLib.idle_add(next, self._fill_completion_dict()) - else: - # Only in that case because it's called by - # self._fill_completion_dict() otherwise - self._load_history(jid, account) - self._ui.results_scrolledwindow.set_visible(False) + def _on_show_status(self, _widget): + # Reload logs + self._on_day_selected(None) - def save_state(self): - x, y = self.get_window().get_root_origin() + def _save_state(self): + x_pos, y_pos = self.get_window().get_root_origin() width, height = self.get_size() - - app.settings.set('history_window_x-position', x) - app.settings.set('history_window_y-position', y) + app.settings.set('history_window_x-position', x_pos) + app.settings.set('history_window_y-position', y_pos) app.settings.set('history_window_width', width) app.settings.set('history_window_height', height)