removed forgotten debug message
[bioacid.git] / tklog.d
blobc0c98e3a0186de33cfd78c854b90611ecd8252ac
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, version 3 of the License ONLY.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 module tklog is aliced;
18 import std.datetime;
20 import arsd.color;
21 import arsd.image;
22 import arsd.simpledisplay;
24 import iv.cmdcon;
25 import iv.cmdcon.gl;
26 import iv.nanovega;
27 import iv.nanovega.blendish;
28 import iv.nanovega.textlayouter;
29 import iv.strex;
30 import iv.tox;
31 import iv.unarray;
32 import iv.utfutil;
33 import iv.vfs.io;
35 import accdb;
36 import accobj;
37 import fonts;
38 import popups;
39 import icondata;
40 import notifyicon;
41 import toxproto;
43 import tkmain;
46 // ////////////////////////////////////////////////////////////////////////// //
47 alias LayTextClass = LayTextD;
50 // ////////////////////////////////////////////////////////////////////////// //
51 __gshared LayTextClass lay;
52 __gshared int layWinHeight = 0;
53 __gshared int layOffset = 0; // from bottom
55 static struct LayUrl {
56 string url;
57 union {
58 usize refcount;
59 usize nextFree; // index+1
62 @property bool isFree () const nothrow @safe @nogc => (url.length == 0);
65 private __gshared usize[string] layUrlIndex;
66 private __gshared usize layUrlFreeHead = 0;
67 private __gshared LayUrl[] layUrlArray;
70 private usize allocUrl (string url) {
71 usize res = layUrlFreeHead;
72 if (res) {
73 --res; // normalize
74 layUrlFreeHead = layUrlArray[res].nextFree;
75 } else {
76 res = layUrlArray.length;
77 layUrlArray.length += 1;
78 layUrlArray.assumeSafeAppend;
80 layUrlArray[res].url = url;
81 layUrlArray[res].refcount = 1;
82 layUrlIndex[url] = res;
83 return res;
87 private void freeUrl (usize idx) {
88 assert(idx < layUrlArray.length);
89 assert(!layUrlArray[idx].isFree);
90 if (--layUrlArray[idx].refcount) return;
91 //conwriteln("FREE URL idx=", idx, "; url=[", layUrlArray[idx].url, "]");
92 layUrlIndex.remove(layUrlArray[idx].url);
93 layUrlArray[idx].url = null;
94 layUrlArray[idx].nextFree = layUrlFreeHead;
95 layUrlFreeHead = idx+1;
99 string findUrlByWordIndex (uint widx) {
100 auto w = lay.wordByIndex(widx);
101 if (w is null) return null;
102 usize pos = w.udata;
103 if (pos < 1 || pos > layUrlArray.length) return null;
104 //conwriteln(" pos=", pos, "; url=[", layUrlArray[pos-1].url, "]");
105 --pos;
106 return layUrlArray[pos].url;
110 private void appendUrl (uint widx, string url) {
111 auto w = lay.wordByIndex(widx);
112 if (w is null) return;
113 assert(w.udata == 0);
114 if (url.length == 0) return;
115 auto ip = url in layUrlIndex;
116 if (ip) {
117 assert(!layUrlArray[*ip].isFree);
118 ++layUrlArray[*ip].refcount;
119 w.udata = (*ip)+1;
120 //conwriteln("OLD URL widx=", widx, "; udata=", w.udata, "; rc=", layUrlArray[*ip].refcount, "; url=[", layUrlArray[*ip].url, "]");
121 } else {
122 usize pos = allocUrl(url);
123 w.udata = pos+1;
124 //conwriteln("NEW URL widx=", widx, "; udata=", w.udata, "; rc=", layUrlArray[pos].refcount, "; url=[", layUrlArray[pos].url, "]");
129 private void urlWordsRemoved (uint widx, int count) {
130 if (count < 1) return;
131 foreach (uint wpos; widx..widx+count) {
132 auto w = lay.wordByIndex(wpos);
133 if (w is null) continue;
134 usize pos = w.udata;
135 if (!pos) continue;
136 --pos;
137 assert(pos < layUrlArray.length);
138 assert(!layUrlArray[pos].isFree);
139 assert(layUrlArray[pos].refcount);
140 //conwriteln("FREE WORD URL widx=", wpos, "; udata=", w.udata, "; rc=", layUrlArray[pos].refcount, "; url=[", layUrlArray[pos].url, "]");
141 freeUrl(pos);
146 private void urlWipeAll () {
147 layUrlArray.unsafeArrayClear();
148 layUrlIndex.clear();
149 layUrlFreeHead = 0;
153 // ////////////////////////////////////////////////////////////////////////// //
154 class MessageStart : LayObject {
155 long msgid; // >0: outgoing, unacked yet
157 this (long aid=-1) nothrow @safe @nogc { msgid = aid; }
159 override int width () => 0;
160 override int spacewidth () => 0;
161 override int height () => 0;
162 override int ascent () => 0;
163 override int descent () => 0;
164 override bool canbreak () => true;
165 override bool spaced () => false;
166 // y is at baseline
167 override void draw (NVGContext ctx, float x, float y) {}
171 class MessageDividerStart : LayObject {
172 long msgid; // >0: outgoing, unacked yet
174 this (long aid=-1) nothrow @safe @nogc { msgid = aid; }
176 override int width () => 0;
177 override int spacewidth () => 0;
178 override int height () => 0;
179 override int ascent () => 0;
180 override int descent () => 0;
181 override bool canbreak () => true;
182 override bool spaced () => false;
183 // y is at baseline
184 override void draw (NVGContext ctx, float x, float y) {}
188 class MessageOutMark : LayObject {
189 long msgid; // >=0: outgoing, unacked yet (0: sent in offline mode)
190 TextDigest digest; // text digest
191 SysTime time;
193 this (long aid, SysTime atime, const(void)[] atext) nothrow @safe @nogc { msgid = aid; time = atime; digest = textDigest(atext); }
195 override int width () => kittyOut.width;
196 override int spacewidth () => 4;
197 override int height () => kittyOut.height;
198 override int ascent () => 0;
199 override int descent () => 0;
200 override bool canbreak () => true;
201 override bool spaced () => false;
202 // y is at baseline
203 override void draw (NVGContext ctx, float x, float y) {
204 if (msgid >= 0) {
205 nvg.save();
206 scope(exit) nvg.restore;
207 nvg.newPath();
208 // +3 is a hack
209 nvg.rect(x, y-height+3, width, height);
210 nvg.fillPaint(nvg.imagePattern(x, y-height+3, width, height, 0, kittyOut));
211 nvg.fill();
215 final bool isOurMark (const(void)[] atext, SysTime atime) nothrow @trusted {
216 pragma(inline, true);
217 return (atime == time && digest[] == textDigest(atext));
220 final bool isOurMark (long aid, const(void)[] atext, SysTime atime) nothrow @trusted {
221 pragma(inline, true);
222 return (aid > 0 ? (msgid == aid) : (atime == time && digest[] == textDigest(atext)));
227 // ////////////////////////////////////////////////////////////////////////// //
228 void logFixAckMessageId (long oldid, long newid, const(void)[] text, SysTime time) {
229 if (oldid == newid) return; // nothing to do
230 foreach (immutable uint widx; 0..lay.wordCount) {
231 auto w = lay.wordByIndex(widx);
232 int oidx = w.objectIdx;
233 if (oidx >= 0) {
234 if (auto maw = cast(MessageOutMark)lay.objectAtIndex(oidx)) {
235 if (maw.isOurMark(oldid, text, time)) {
236 maw.msgid = newid;
237 glconPostScreenRepaint(); // redraw
244 void logResetAckMessageIds () {
245 bool doRefresh = false;
246 foreach (immutable uint widx; 0..lay.wordCount) {
247 auto w = lay.wordByIndex(widx);
248 int oidx = w.objectIdx;
249 if (oidx >= 0) {
250 if (auto maw = cast(MessageOutMark)lay.objectAtIndex(oidx)) {
251 if (maw.msgid > 0) {
252 maw.msgid = 0;
253 doRefresh = true;
258 if (doRefresh) glconPostScreenRepaint(); // redraw
261 void ackLogMessage (long msgid) {
262 if (msgid <= 0) return;
263 foreach (immutable uint widx; 0..lay.wordCount) {
264 auto w = lay.wordByIndex(widx);
265 int oidx = w.objectIdx;
266 if (oidx >= 0) {
267 if (auto maw = cast(MessageOutMark)lay.objectAtIndex(oidx)) {
268 if (maw.msgid == msgid) {
269 maw.msgid = -1; // reset mark
270 glconPostScreenRepaint(); // redraw
278 // coordinates are adjusted so (0, 0) points to logical layouter top-left
280 bool logCheckCancelMark (int mx, int my) {
281 bool removeFromResendQueue (long msgid, in ref TextDigest digest, SysTime time) {
285 // ////////////////////////////////////////////////////////////////////////// //
286 void wipeLog () {
287 lay.wipeAll(true); // clear log, but delete objects
288 urlWipeAll();
289 layOffset = 0;
293 // ////////////////////////////////////////////////////////////////////////// //
294 void addDividerLine (bool doflushgui=false) {
295 if (glconCtlWindow is null || glconCtlWindow.closed) return;
298 bool inFrame = nvg.inFrame;
299 if (!inFrame) {
300 glconCtlWindow.setAsCurrentOpenGlContext(); // make this window active
301 nvg.beginFrame(glconCtlWindow.width, glconCtlWindow.height);
303 scope(exit) {
304 if (!inFrame) {
305 nvg.endFrame();
306 if (doflushgui) flushGui();
307 glconCtlWindow.releaseCurrentOpenGlContext();
311 uint widx = 0;
312 while (widx < lay.wordCount) {
313 auto w = lay.wordByIndex(widx);
314 int oidx = w.objectIdx;
315 if (oidx >= 0) {
316 if (auto maw = cast(MessageDividerStart)lay.objectAtIndex(oidx)) {
317 // this always followed by expander; remove them both
318 lay.removeWordsAt(widx, 2);
319 urlWordsRemoved(widx, 2);
320 continue;
323 ++widx;
326 lay.fontStyle.fontsize = 2;
327 lay.fontStyle.color = NVGColor.k8orange.asUint;
328 lay.fontStyle.bgcolor = NVGColor("#aa0").asUint;
329 lay.fontStyle.monospace = true;
331 lay.putObject(new MessageDividerStart());
332 lay.putExpander();
333 lay.endPara();
334 lay.finalize();
337 // redraw
338 glconPostScreenRepaint();
342 // ////////////////////////////////////////////////////////////////////////// //
343 // `ct` can be `null` for "my message" or "system message"
344 void addTextToLog (Account acc, Contact ct, LogFile.Msg.Kind kind, bool action, const(char)[] msg, SysTime time, long msgid=-1, bool doflushgui=false) {
345 if (glconCtlWindow is null || glconCtlWindow.closed) return;
348 bool inFrame = nvg.inFrame;
349 if (!inFrame) {
350 glconCtlWindow.setAsCurrentOpenGlContext(); // make this window active
351 nvg.beginFrame(glconCtlWindow.width, glconCtlWindow.height);
353 scope(exit) {
354 if (!inFrame) {
355 nvg.endFrame();
356 if (doflushgui) flushGui();
357 glconCtlWindow.releaseCurrentOpenGlContext();
361 // add "message start" mark
362 lay.putObject(new MessageStart(msgid));
364 lay.fontStyle.fontsize = 16;
365 lay.fontStyle.color = NVGColor.k8orange.asUint;
366 //lay.fontStyle.bgcolor = NVGColor("#222").asUint;
367 lay.fontStyle.bgcolor = NVGColor("#5888").asUint;
368 lay.fontStyle.monospace = true;
370 NVGColor textColor;
372 final switch (kind) {
373 case LogFile.Msg.Kind.Outgoing: textColor = NVGColor.k8orange; lay.fontStyle.color = NVGColor("#c40").asUint; lay.put(acc.info.nick); break;
374 case LogFile.Msg.Kind.Incoming: textColor = NVGColor("#aaa"); lay.fontStyle.color = NVGColor("#777").asUint; lay.put(ct.info.nick); break;
375 case LogFile.Msg.Kind.Notification: textColor = NVGColor("#0c0"); lay.fontStyle.color = textColor.asUint; lay.put("*system*"); break;
378 lay.fontStyle.monospace = false;
379 //lay.putHardSpace(64);
380 lay.putExpander();
381 // add "message outgoing" mark
382 if (kind == LogFile.Msg.Kind.Outgoing) {
383 //conwriteln("OG: msgid=", msgid);
384 if (msgid <= 0 && ct !is null) {
385 msgid = ct.findInResendQueue(msg, time);
386 //conwriteln(" new msgid=", msgid);
388 lay.putObject(new MessageOutMark(msgid, time, msg));
389 lay.putExpander();
392 if (kind == LogFile.Msg.Kind.Incoming) lay.fontStyle.color = NVGColor("#888").asUint;
394 import std.datetime;
395 import std.format : format;
396 auto dt = cast(DateTime)time;
397 string tstr = "%04u/%02u/%02u".format(dt.year, dt.month, dt.day);
398 lay.put(tstr);
399 lay.putNBSP();
402 lay.fontStyle.color = (kind == LogFile.Msg.Kind.Incoming ? NVGColor("#888").asUint : NVGColor("#666").asUint);
403 lay.fontStyle.bgcolor = NVGColor("#006").asUint;
405 import std.datetime;
406 import std.format : format;
407 auto dt = cast(DateTime)time;
408 string tstr = "%02u:%02u:%02u".format(dt.hour, dt.minute, dt.second);
409 lay.put(tstr);
410 lay.putNBSP();
412 lay.endPara();
414 lay.fontStyle.bgcolor = NVGColor.transparent.asUint;
415 lay.fontStyle.fontsize = 20;
417 if (action) {
418 lay.fontStyle.color = NVGColor("#fff").asUint;
419 lay.put("/me ");
422 void xput (const(char)[] str) {
423 while (str.length) {
424 auto nl = str.indexOf('\n');
425 if (nl < 0) { lay.put(str); break; }
426 if (nl > 0) lay.put(str[0..nl]);
427 lay.endPara();
428 str = str[nl+1..$];
432 msg = msg.xstripright;
434 lay.fontStyle.color = textColor.asUint;
435 while (msg.length) {
436 auto nfo = urlDetect(msg);
437 if (!nfo.valid) {
438 xput(msg);
439 break;
441 // url found
442 xput(msg[0..nfo.pos]);
443 string url = msg[nfo.pos..nfo.end].idup;
444 msg = msg[nfo.end..$];
445 auto stword = lay.nextWordIndex;
446 lay.pushStyles();
447 auto c = lay.fontStyle.color;
448 scope(exit) { lay.popStyles; lay.fontStyle.color = c; }
449 lay.fontStyle.href = true;
450 lay.fontStyle.underline = true;
451 lay.fontStyle.color = NVGColor("#06f").asUint;
452 lay.put(url);
453 if (msg.length) {
454 lay.endWord(spaced:(msg[0] <= ' ' && msg[0] != '\n'));
455 } else {
456 lay.endWord(spaced:false);
458 while (stword < lay.nextWordIndex) {
459 appendUrl(stword, url);
460 ++stword;
464 lay.endPara();
465 lay.finalize();
467 // redraw
468 glconPostScreenRepaint();
472 void addTextToLog (Account acc, Contact ct, in ref LogFile.Msg msg, long msgid=-1, bool doflushgui=false) {
473 import iv.utfutil : utf8Encode;
474 char[] text;
475 text.reserve(4096);
476 scope(exit) delete text;
477 // decode text
478 foreach (dchar dc; msg.byDChar) {
479 char[4] buf = void;
480 auto len = utf8Encode(buf[], dc);
481 text ~= buf[0..len];
483 addTextToLog(acc, ct, msg.kind, msg.isMe, text, msg.time, msgid, doflushgui);