bigfix: BioAcid should not spuriously resend unacked messages before ack arrives
[bioacid.git] / tklog.d
blobb6ab3e53bf09630246848fd32d6601bfb80e89b8
1 /* coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
2 * Understanding is not required. Only obedience.
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 module tklog is aliced;
19 import std.datetime;
21 import arsd.color;
22 import arsd.image;
23 import arsd.simpledisplay;
25 import iv.cmdcon;
26 import iv.cmdcon.gl;
27 import iv.nanovega;
28 import iv.nanovega.blendish;
29 import iv.nanovega.textlayouter;
30 import iv.strex;
31 import iv.tox;
32 import iv.unarray;
33 import iv.utfutil;
34 import iv.vfs.io;
36 import accdb;
37 import accobj;
38 import fonts;
39 import popups;
40 import icondata;
41 import notifyicon;
42 import toxproto;
44 import tkmain;
47 // ////////////////////////////////////////////////////////////////////////// //
48 alias LayTextClass = LayTextD;
51 // ////////////////////////////////////////////////////////////////////////// //
52 __gshared LayTextClass lay;
53 __gshared int layWinHeight = 0;
54 __gshared int layOffset = 0; // from bottom
56 static struct LayUrl {
57 string url;
58 uint wordidx; // first word
61 __gshared LayUrl[uint] layUrlList; // for each word
64 // ////////////////////////////////////////////////////////////////////////// //
65 class MessageStart : LayObject {
66 long msgid; // >0: outgoing, unacked yet
68 this (long aid=-1) nothrow @safe @nogc { msgid = aid; }
70 override int width () => 0;
71 override int spacewidth () => 0;
72 override int height () => 0;
73 override int ascent () => 0;
74 override int descent () => 0;
75 override bool canbreak () => true;
76 override bool spaced () => false;
77 // y is at baseline
78 override void draw (NVGContext ctx, float x, float y) {}
82 class MessageDividerStart : LayObject {
83 long msgid; // >0: outgoing, unacked yet
85 this (long aid=-1) nothrow @safe @nogc { msgid = aid; }
87 override int width () => 0;
88 override int spacewidth () => 0;
89 override int height () => 0;
90 override int ascent () => 0;
91 override int descent () => 0;
92 override bool canbreak () => true;
93 override bool spaced () => false;
94 // y is at baseline
95 override void draw (NVGContext ctx, float x, float y) {}
99 class MessageOutMark : LayObject {
100 long msgid; // >=0: outgoing, unacked yet (0: sent in offline mode)
101 TextDigest digest; // text digest
102 SysTime time;
104 this (long aid, SysTime atime, const(void)[] atext) nothrow @safe @nogc { msgid = aid; time = atime; digest = textDigest(atext); }
106 override int width () => kittyOut.width;
107 override int spacewidth () => 4;
108 override int height () => kittyOut.height;
109 override int ascent () => 0;
110 override int descent () => 0;
111 override bool canbreak () => true;
112 override bool spaced () => false;
113 // y is at baseline
114 override void draw (NVGContext ctx, float x, float y) {
115 if (msgid >= 0) {
116 nvg.save();
117 scope(exit) nvg.restore;
118 nvg.newPath();
119 // +3 is a hack
120 nvg.rect(x, y-height+3, width, height);
121 nvg.fillPaint(nvg.imagePattern(x, y-height+3, width, height, 0, kittyOut));
122 nvg.fill();
126 final bool isOurMark (const(void)[] atext, SysTime atime) nothrow @trusted {
127 pragma(inline, true);
128 return (atime == time && digest[] == textDigest(atext));
131 final bool isOurMark (long aid, const(void)[] atext, SysTime atime) nothrow @trusted {
132 pragma(inline, true);
133 return (aid > 0 ? (msgid == aid) : (atime == time && digest[] == textDigest(atext)));
138 // ////////////////////////////////////////////////////////////////////////// //
139 void logFixAckMessageId (long oldid, long newid, const(void)[] text, SysTime time) {
140 if (oldid == newid) return; // nothing to do
141 foreach (immutable uint widx; 0..lay.wordCount) {
142 auto w = lay.wordByIndex(widx);
143 int oidx = w.objectIdx;
144 if (oidx >= 0) {
145 if (auto maw = cast(MessageOutMark)lay.objectAtIndex(oidx)) {
146 if (maw.isOurMark(oldid, text, time)) {
147 maw.msgid = newid;
148 glconPostScreenRepaint(); // redraw
155 void logResetAckMessageIds () {
156 bool doRefresh = false;
157 foreach (immutable uint widx; 0..lay.wordCount) {
158 auto w = lay.wordByIndex(widx);
159 int oidx = w.objectIdx;
160 if (oidx >= 0) {
161 if (auto maw = cast(MessageOutMark)lay.objectAtIndex(oidx)) {
162 if (maw.msgid > 0) {
163 maw.msgid = 0;
164 doRefresh = true;
169 if (doRefresh) glconPostScreenRepaint(); // redraw
172 void ackLogMessage (long msgid) {
173 if (msgid <= 0) return;
174 foreach (immutable uint widx; 0..lay.wordCount) {
175 auto w = lay.wordByIndex(widx);
176 int oidx = w.objectIdx;
177 if (oidx >= 0) {
178 if (auto maw = cast(MessageOutMark)lay.objectAtIndex(oidx)) {
179 if (maw.msgid == msgid) {
180 maw.msgid = -1; // reset mark
181 glconPostScreenRepaint(); // redraw
189 // coordinates are adjusted so (0, 0) points to logical layouter top-left
191 bool logCheckCancelMark (int mx, int my) {
192 bool removeFromResendQueue (long msgid, in ref TextDigest digest, SysTime time) {
196 // ////////////////////////////////////////////////////////////////////////// //
197 void wipeLog () {
198 lay.wipeAll(true); // clear log, but delete objects
199 layUrlList.clear();
200 layOffset = 0;
204 // ////////////////////////////////////////////////////////////////////////// //
205 void addDividerLine (bool doflushgui=false) {
206 if (glconCtlWindow is null || glconCtlWindow.closed) return;
209 bool inFrame = nvg.inFrame;
210 if (!inFrame) {
211 glconCtlWindow.setAsCurrentOpenGlContext(); // make this window active
212 nvg.beginFrame(glconCtlWindow.width, glconCtlWindow.height);
214 scope(exit) {
215 if (!inFrame) {
216 nvg.endFrame();
217 if (doflushgui) flushGui();
218 glconCtlWindow.releaseCurrentOpenGlContext();
222 uint widx = 0;
223 while (widx < lay.wordCount) {
224 auto w = lay.wordByIndex(widx);
225 int oidx = w.objectIdx;
226 if (oidx >= 0) {
227 if (auto maw = cast(MessageDividerStart)lay.objectAtIndex(oidx)) {
228 // this always followed by expander; remove them both
229 lay.removeWordsAt(widx, 2);
230 continue;
233 ++widx;
236 lay.fontStyle.fontsize = 2;
237 lay.fontStyle.color = NVGColor.k8orange.asUint;
238 lay.fontStyle.bgcolor = NVGColor("#aa0").asUint;
239 lay.fontStyle.monospace = true;
241 lay.putObject(new MessageDividerStart());
242 lay.putExpander();
243 lay.endPara();
244 lay.finalize();
247 // redraw
248 glconPostScreenRepaint();
252 // ////////////////////////////////////////////////////////////////////////// //
253 // `ct` can be `null` for "my message" or "system message"
254 void addTextToLog (Account acc, Contact ct, LogFile.Msg.Kind kind, bool action, const(char)[] msg, SysTime time, long msgid=-1, bool doflushgui=false) {
255 if (glconCtlWindow is null || glconCtlWindow.closed) return;
258 bool inFrame = nvg.inFrame;
259 if (!inFrame) {
260 glconCtlWindow.setAsCurrentOpenGlContext(); // make this window active
261 nvg.beginFrame(glconCtlWindow.width, glconCtlWindow.height);
263 scope(exit) {
264 if (!inFrame) {
265 nvg.endFrame();
266 if (doflushgui) flushGui();
267 glconCtlWindow.releaseCurrentOpenGlContext();
271 // add "message start" mark
272 lay.putObject(new MessageStart(msgid));
274 lay.fontStyle.fontsize = 16;
275 lay.fontStyle.color = NVGColor.k8orange.asUint;
276 //lay.fontStyle.bgcolor = NVGColor("#222").asUint;
277 lay.fontStyle.bgcolor = NVGColor("#5888").asUint;
278 lay.fontStyle.monospace = true;
280 NVGColor textColor;
282 final switch (kind) {
283 case LogFile.Msg.Kind.Outgoing: textColor = NVGColor.k8orange; lay.fontStyle.color = NVGColor("#c40").asUint; lay.put(acc.info.nick); break;
284 case LogFile.Msg.Kind.Incoming: textColor = NVGColor("#aaa"); lay.fontStyle.color = NVGColor("#777").asUint; lay.put(ct.info.nick); break;
285 case LogFile.Msg.Kind.Notification: textColor = NVGColor("#0c0"); lay.fontStyle.color = textColor.asUint; lay.put("*system*"); break;
288 lay.fontStyle.monospace = false;
289 //lay.putHardSpace(64);
290 lay.putExpander();
291 // add "message outgoing" mark
292 if (kind == LogFile.Msg.Kind.Outgoing) {
293 //conwriteln("OG: msgid=", msgid);
294 if (msgid <= 0 && ct !is null) {
295 msgid = ct.findInResendQueue(msg, time);
296 //conwriteln(" new msgid=", msgid);
298 lay.putObject(new MessageOutMark(msgid, time, msg));
299 lay.putExpander();
302 if (kind == LogFile.Msg.Kind.Incoming) lay.fontStyle.color = NVGColor("#888").asUint;
304 import std.datetime;
305 import std.format : format;
306 auto dt = cast(DateTime)time;
307 string tstr = "%04u/%02u/%02u".format(dt.year, dt.month, dt.day);
308 lay.put(tstr);
309 lay.putNBSP();
312 lay.fontStyle.color = (kind == LogFile.Msg.Kind.Incoming ? NVGColor("#888").asUint : NVGColor("#666").asUint);
313 lay.fontStyle.bgcolor = NVGColor("#006").asUint;
315 import std.datetime;
316 import std.format : format;
317 auto dt = cast(DateTime)time;
318 string tstr = "%02u:%02u:%02u".format(dt.hour, dt.minute, dt.second);
319 lay.put(tstr);
320 lay.putNBSP();
322 lay.endPara();
324 lay.fontStyle.bgcolor = NVGColor.transparent.asUint;
325 lay.fontStyle.fontsize = 20;
327 if (action) {
328 lay.fontStyle.color = NVGColor("#fff").asUint;
329 lay.put("/me ");
332 void xput (const(char)[] str) {
333 while (str.length) {
334 auto nl = str.indexOf('\n');
335 if (nl < 0) { lay.put(str); break; }
336 if (nl > 0) lay.put(str[0..nl]);
337 lay.endPara();
338 str = str[nl+1..$];
342 msg = msg.xstripright;
344 lay.fontStyle.color = textColor.asUint;
345 while (msg.length) {
346 auto nfo = urlDetect(msg);
347 if (!nfo.valid) {
348 xput(msg);
349 break;
351 // url found
352 xput(msg[0..nfo.pos]);
353 string url = msg[nfo.pos..nfo.end].idup;
354 msg = msg[nfo.end..$];
355 auto stword = lay.nextWordIndex;
356 lay.pushStyles();
357 auto c = lay.fontStyle.color;
358 scope(exit) { lay.popStyles; lay.fontStyle.color = c; }
359 lay.fontStyle.href = true;
360 lay.fontStyle.underline = true;
361 lay.fontStyle.color = NVGColor("#06f").asUint;
362 lay.put(url);
363 while (stword < lay.nextWordIndex) {
364 layUrlList[stword] = LayUrl(url, stword);
365 ++stword;
369 lay.endPara();
370 lay.finalize();
372 // redraw
373 glconPostScreenRepaint();
377 void addTextToLog (Account acc, Contact ct, in ref LogFile.Msg msg, long msgid=-1, bool doflushgui=false) {
378 import iv.utfutil : utf8Encode;
379 char[] text;
380 text.reserve(4096);
381 scope(exit) delete text;
382 // decode text
383 foreach (dchar dc; msg.byDChar) {
384 char[4] buf = void;
385 auto len = utf8Encode(buf[], dc);
386 text ~= buf[0..len];
388 addTextToLog(acc, ct, msg.kind, msg.isMe, text, msg.time, msgid, doflushgui);