[polari/wip/raresv/blankState] chatView: add the blank state feature



commit a793ec9bfe3df03e2152a00514ca5af820e71c3e
Author: Rares Visalom <rares visalom gmail com>
Date:   Tue Nov 29 00:43:37 2016 +0200

    chatView: add the blank state feature
    
    The blank state marks the beginning of the history
    or chat logs in that specific room. It is also
    a way of telling the user what the chatroom is used
    for and provide any additional information like the
    name and topic of the current room.
    
    The blank state is inserted automatically when the
    beginning of the log history is reached. It is not
    inserted within the log itself, but rather inserted
    on demand. It is only inserted after the connection
    is established.

 data/org.gnome.Polari.data.gresource.xml |    2 +
 data/resources/polari-paste-warning.svg  |   22 +++++
 data/resources/polari-room-hidden.svg    |   22 +++++
 src/chatView.js                          |  136 +++++++++++++++++++++++++++++-
 4 files changed, 181 insertions(+), 1 deletions(-)
---
diff --git a/data/org.gnome.Polari.data.gresource.xml b/data/org.gnome.Polari.data.gresource.xml
index b21bb85..3e2ecab 100644
--- a/data/org.gnome.Polari.data.gresource.xml
+++ b/data/org.gnome.Polari.data.gresource.xml
@@ -6,6 +6,8 @@
     <file alias="gtk/help-overlay.ui" preprocess="xml-stripblanks">resources/help-overlay.ui</file>
     <file alias="gtk/menus.ui" preprocess="xml-stripblanks">resources/menus.ui</file>
     <file alias="icons/polari-user-notify-symbolic.svg" 
preprocess="xml-stripblanks">resources/polari-user-notify-symbolic.svg</file>
+    <file alias="icons/polari-paste-warning.svg" 
preprocess="xml-stripblanks">resources/polari-paste-warning.svg</file>
+    <file alias="icons/polari-room-hidden.svg" 
preprocess="xml-stripblanks">resources/polari-room-hidden.svg</file>
     <file alias="ui/connection-details.ui" 
preprocess="xml-stripblanks">resources/connection-details.ui</file>
     <file alias="ui/connection-properties.ui" 
preprocess="xml-stripblanks">resources/connection-properties.ui</file>
     <file alias="ui/entry-area.ui" preprocess="xml-stripblanks">resources/entry-area.ui</file>
diff --git a/data/resources/polari-paste-warning.svg b/data/resources/polari-paste-warning.svg
new file mode 100644
index 0000000..181e412
--- /dev/null
+++ b/data/resources/polari-paste-warning.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns:dc="http://purl.org/dc/elements/1.1/"; xmlns:cc="http://creativecommons.org/ns#"; 
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"; xmlns:svg="http://www.w3.org/2000/svg"; 
xmlns="http://www.w3.org/2000/svg"; xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"; 
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"; version="1.1" id="svg10865" viewBox="0 0 
15.999999 15.999999" height="4.5155554mm" width="4.5155554mm" inkscape:version="0.92.0 r" 
sodipodi:docname="polari-paste-warning.svg">
+  <sodipodi:namedview pagecolor="#ffffff" bordercolor="#666666" borderopacity="1" objecttolerance="10" 
gridtolerance="10" guidetolerance="10" inkscape:pageopacity="0" inkscape:pageshadow="2" 
inkscape:window-width="1920" inkscape:window-height="1016" id="namedview10" showgrid="true" 
inkscape:zoom="29.500001" inkscape:cx="10.984779" inkscape:cy="8.6969692" inkscape:window-x="0" 
inkscape:window-y="27" inkscape:window-maximized="0" inkscape:current-layer="svg10865">
+    <inkscape:grid type="xygrid" id="grid7674"/>
+  </sodipodi:namedview>
+  <defs id="defs10867"/>
+  <metadata id="metadata10870">
+    <rdf:RDF>
+      <cc:Work rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+        <dc:title/>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g style="display:inline" inkscape:label="task-due" id="g41006-4-1-9" 
transform="translate(-80.0001,-517.98241)" inkscape:export-xdpi="90" inkscape:export-ydpi="90">
+    <rect width="16" height="16" x="-96" y="518" id="rect41008-3-9-7" 
style="color:#bebebe;display:inline;overflow:visible;visibility:visible;fill:none;stroke:none;stroke-width:1;marker:none;enable-background:new"
 transform="scale(-1,1)"/>
+    <path style="fill:#bebebe;fill-opacity:1;stroke:none" d="m -80.5,181 c -0.293087,0 -0.646731,0.036 
-1,0.3125 -0.353269,0.27647 -0.53125,0.76875 -0.53125,1.125 v 12.03125 c 0,0.384 0.127689,0.81517 
0.4375,1.125 0.285418,0.28544 0.709136,0.38602 1.09375,0.375 v 0.0312 h 0.09375 4.4375 v -2 h -4.0625 v -11 h 
0.96875 v -2 H -80.5 Z m 8.46875,0 v 2 H -71 v 4.03125 h 2 V 182.375 c 0,-0.33333 -0.118934,-0.72959 
-0.40625,-1 C -69.693566,181.10459 -70.075149,181 -70.375,181 Z m -6.9375,4.03125 V 192 h 3 v -4.96875 H -72 
v -2 z" transform="translate(163.0002,338)" id="rect4897-9-5-9" inkscape:connector-curvature="0"/>
+    <rect style="fill:#bebebe;fill-opacity:1;stroke:none" id="rect4899-7-0-9" width="5.0625052" height="4" 
x="84.999992" y="518" rx="0.53033006" ry="0.53033006"/>
+    <path inkscape:connector-curvature="0" class="warning" 
style="color:#bebebe;display:inline;overflow:visible;visibility:visible;fill:#f57900;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;marker:none"
 d="m 88.8752,526 c -0.49245,0 -0.875,0.38256 -0.875,0.875 v 6.25 c 0,0.49244 0.38255,0.875 0.875,0.875 h 
6.25 c 0.49245,0 0.875,-0.38256 0.875,-0.875 v -6.25 c 0,-0.49244 -0.38255,-0.875 -0.875,-0.875 z m 2.125,1 h 
2 v 4 h -2 z m 0,5 h 2 v 1 h -2 z" id="path10898-3-1-1-6-0-3" sodipodi:nodetypes="ccccccccccccccccccc"/>
+  </g>
+</svg>
\ No newline at end of file
diff --git a/data/resources/polari-room-hidden.svg b/data/resources/polari-room-hidden.svg
new file mode 100644
index 0000000..ae08789
--- /dev/null
+++ b/data/resources/polari-room-hidden.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns:dc="http://purl.org/dc/elements/1.1/"; xmlns:cc="http://creativecommons.org/ns#"; 
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"; xmlns:svg="http://www.w3.org/2000/svg"; 
xmlns="http://www.w3.org/2000/svg"; xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"; 
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"; version="1.1" id="svg10865" viewBox="0 0 
15.999999 15.999999" height="4.5155554mm" width="4.5155554mm" inkscape:version="0.92.0 r" 
sodipodi:docname="polari-room-hidden.svg">
+  <sodipodi:namedview pagecolor="#ffffff" bordercolor="#666666" borderopacity="1" objecttolerance="10" 
gridtolerance="10" guidetolerance="10" inkscape:pageopacity="0" inkscape:pageshadow="2" 
inkscape:window-width="1920" inkscape:window-height="1016" id="namedview10" showgrid="true" 
inkscape:zoom="29.500001" inkscape:cx="6.1034231" inkscape:cy="9.6122234" inkscape:window-x="0" 
inkscape:window-y="27" inkscape:window-maximized="0" inkscape:current-layer="svg10865">
+    <inkscape:grid type="xygrid" id="grid7674"/>
+  </sodipodi:namedview>
+  <defs id="defs10867"/>
+  <metadata id="metadata10870">
+    <rdf:RDF>
+      <cc:Work rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+        <dc:title/>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g style="display:inline" id="g13514-2" transform="translate(2155.3572,-5955.9073)" 
inkscape:export-xdpi="90" inkscape:export-ydpi="90">
+    <path 
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:#919191;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
 d="m -2139.3572,5963.8726 c 0.022,1.0153 -4.4656,4.4673 -8,4.4894 -3.5344,0.022 -8,-3.4741 -8,-4.4894 
0,-1.0153 4.4877,-4.5106 8,-4.5106 3.5123,0 7.9779,3.4953 8,4.5106 z" id="path10603-4" 
inkscape:connector-curvature="0" sodipodi:nodetypes="zzzzz"/>
+    <circle 
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#919191;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
 id="circle10605-3" cx="-2147.3572" cy="5963.8618" r="2.999995"/>
+    <circle 
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:#919191;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
 id="circle10607-7" cx="-2147.3572" cy="5963.8618" r="1.499995"/>
+    <path inkscape:connector-curvature="0" id="path13474-1" d="m -2140.0938,5959.0977 -15.1601,8.7519 
0.5,0.8672 15.1601,-8.7539 z" 
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1
 
;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"/>
+  </g>
+</svg>
\ No newline at end of file
diff --git a/src/chatView.js b/src/chatView.js
index 05dbdab..22f9aa8 100644
--- a/src/chatView.js
+++ b/src/chatView.js
@@ -8,6 +8,7 @@ const PangoCairo = imports.gi.PangoCairo;
 const Polari = imports.gi.Polari;
 const Tp = imports.gi.TelepathyGLib;
 const Tpl = imports.gi.TelepathyLogger;
+const GdkPixbuf = imports.gi.GdkPixbuf;
 
 const Lang = imports.lang;
 const Mainloop = imports.mainloop;
@@ -16,6 +17,7 @@ const Signals = imports.signals;
 const Utils = imports.utils;
 const UserTracker = imports.userTracker;
 const UserList = imports.userList;
+const RoomManager = imports.roomManager;
 
 const MAX_NICK_CHARS = 8;
 const IGNORE_STATUS_TIME = 5;
@@ -350,6 +352,8 @@ const ChatView = new Lang.Class({
         this._view.connect_after('style-updated',
                                  Lang.bind(this, this._updateIndent));
 
+        this._roomManager = RoomManager.getDefault();
+
         this._room = room;
         this._state = { lastNick: null, lastTimestamp: 0, lastStatusGroup: 0 };
         this._joinTime = 0;
@@ -361,6 +365,12 @@ const ChatView = new Lang.Class({
         this._initialPending = [];
         this._backlogTimeoutId = 0;
         this._statusCount = { left: 0, joined: 0, total: 0 };
+        this._canBlankStateBeInserted = false;
+        this._isBlankStateInserted = false;
+
+        this._room.connect("notify::topic", () => {
+                                    this._canBlankStateBeInserted = true;
+                                    this._tryToInsertBlankState(); });
 
         let statusMonitor = UserTracker.getUserStatusMonitor();
         this._userTracker = statusMonitor.getUserTrackerForAccount(room.account);
@@ -459,7 +469,27 @@ const ChatView = new Lang.Class({
           { name: 'indicator-line',
             pixels_above_lines: 24 },
           { name: 'loading',
-            justification: Gtk.Justification.CENTER }
+            justification: Gtk.Justification.CENTER },
+          {
+            name: 'blank-state-header',
+            left_margin: MARGIN,
+            size: 18000
+          },
+          {
+            name: 'blank-state-topic',
+            left_margin: MARGIN,
+            size: 13000
+          },
+          {
+            name: 'blank-state-tips-image',
+            left_margin: MARGIN,
+            size: 10000
+          },
+          {
+            name: 'blank-state-tips',
+            left_margin: MARGIN,
+            size: 10000
+          }
         ];
         tags.forEach(function(tagProps) {
             tagTable.add(new Gtk.TextTag(tagProps));
@@ -506,6 +536,14 @@ const ChatView = new Lang.Class({
             foreground_rgba: dimColor },
           { name: 'timestamp',
             foreground_rgba: dimColor },
+          { name: 'blank-state-header',
+            foreground_rgba: dimColor },
+          { name: 'blank-state-topic',
+            foreground_rgba: dimColor },
+          { name: 'blank-state-tips-image',
+            foreground_rgba: dimColor },
+          { name: 'blank-state-tips',
+            foreground_rgba: dimColor },
           { name: 'url',
             foreground_rgba: linkColor }
         ];
@@ -731,6 +769,8 @@ const ChatView = new Lang.Class({
     },
 
     _fetchBacklog: function() {
+        this._tryToInsertBlankState();
+
         if (this.vadjustment.value != 0 ||
             this._logWalker.is_end())
             return Gdk.EVENT_PROPAGATE;
@@ -750,6 +790,100 @@ const ChatView = new Lang.Class({
         return Gdk.EVENT_STOP;
     },
 
+    _tryToInsertBlankState: function() {
+        if (this._logWalker.is_end() && !this._isBlankStateInserted && this._canBlankStateBeInserted) {
+            this._insertBlankState();
+            this._isBlankStateInserted = true;
+        }
+    },
+
+    _insertBlankState: function () {
+        let buffer = this._view.get_buffer();
+
+        let blankStateMark = this._view.buffer.create_mark('blank-state-mark',
+                                                           this._view.buffer.get_start_iter(),
+                                                           false);
+
+        let header = "";
+        let topic = "";
+        let image1 = "";
+        let image2 = "";
+        let image3 = "";
+        let tip1 = "";
+        let tip2 = "";
+        let tip3 = "";
+
+        if (this._room.type == Tp.HandleType.CONTACT) {
+            header = 'Conversations with ' + this._room.channel_name + ' start here.\n';
+            topic = 'The following applies to all messages sent here:\n';
+
+            image1 = 'avatar-default-symbolic';
+            image2 = 'polari-room-hidden';
+            image3 = 'polari-paste-warning';
+
+            tip1 = "This is a one-to-one conversation between you and " + this._room.channel_name + ".\n";
+            tip2 = "The conversation is only visible to you and your recipient.\n";
+            tip3 = "This room is private. Use public paste services with care.\n";
+        }
+        else {
+            header = 'Conversations in ' + this._room.channel_name + ' start here.\n';
+            topic = 'The topic of ' + this._room.channel_name + ' is: ' + this._room.topic + '\n';
+
+            image1 = 'polari-user-notify-symbolic';
+            image2 = 'edit-paste-symbolic';
+            image3 = 'emote-love-symbolic';
+
+            tip1 = "Notify other users of your message by including their nickname.\n";
+            tip2 = "Share text and images by pasting them into the text field.\n";
+            tip3 = "If this is your first time using IRC, we recommend glacing over the IRC netiquette.\n";
+        }
+
+        let tags = [this._lookupTag('blank-state-header')];
+        this._insertWithTags(this._view.buffer.get_iter_at_mark(blankStateMark), header, tags);
+
+        tags = [this._lookupTag('blank-state-topic')];
+        this._insertWithTags(this._view.buffer.get_iter_at_mark(blankStateMark),
+                         topic,
+                         tags);
+
+        tags = [this._lookupTag('blank-state-tips')];
+        let imageTags = [this._lookupTag('blank-state-tips-image')];
+        if (this._roomManager.isFirstRun) {
+            this._insertImageAtMarkWithTags(image1, blankStateMark, imageTags);
+            this._insertWithTags(this._view.buffer.get_iter_at_mark(blankStateMark),
+                                 tip1,
+                                 tags);
+
+            this._insertImageAtMarkWithTags(image2, blankStateMark, imageTags);
+            this._insertWithTags(this._view.buffer.get_iter_at_mark(blankStateMark),
+                                 tip2,
+                                 tags);
+
+            this._insertImageAtMarkWithTags(image3, blankStateMark, imageTags);
+            this._insertWithTags(this._view.buffer.get_iter_at_mark(blankStateMark),
+                                 tip3,
+                                 tags);
+        }
+    },
+
+    _insertImageAtMarkWithTags: function(imageName, mark, tags) {
+        let image1 = new Gtk.Image({ icon_name: imageName,
+                                     margin_right: 10,
+                                     visible: true });
+
+        let iter = this._view.buffer.get_iter_at_mark(mark);
+        let offset = iter.get_offset();
+
+        let anchor = this._view.buffer.create_child_anchor(iter);
+        this._view.add_child_at_anchor(image1, anchor);
+
+        let start = this._view.buffer.get_iter_at_offset(offset);
+
+        this._view.buffer.remove_all_tags(start, iter);
+        for (let i = 0; i < tags.length; i++)
+            this._view.buffer.apply_tag(tags[i], start, iter);
+    },
+
     _onValueChanged: function() {
         if (this._valueChangedId)
             return;


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]