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)