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 tkmain
is aliced
;
22 import arsd
.simpledisplay
;
29 import iv
.nanovega
.blendish
;
30 import iv
.nanovega
.textlayouter
;
38 version(sfnt_test
) import iv
.nanovega
.simplefont
;
51 // ////////////////////////////////////////////////////////////////////////// //
52 __gshared
bool mainWindowActive
= false;
53 __gshared
bool mainWindowVisible
= false;
54 __gshared SimpleWindow sdmain
;
55 __gshared CList clist
;
58 void setupToxCoreSender () {
59 toxCoreSendEvent
= delegate (Object msg
) {
60 if (msg
is null) return; // just in case
62 if (glconCtlWindow
is null || glconCtlWindow
.closed
) return;
63 glconCtlWindow
.postEvent(msg
);
64 } catch (Exception e
) {}
69 // ////////////////////////////////////////////////////////////////////////// //
70 string
getBrowserCommand (bool forceOpera
=false) {
71 __gshared string browser
;
72 if (forceOpera
) return "opera";
73 if (browser
.length
== 0) {
74 import core
.stdc
.stdlib
: getenv
;
75 const(char)* evar
= getenv("BROWSER");
76 if (evar
!is null && evar
[0]) {
77 import std
.string
: fromStringz
;
78 browser
= evar
.fromStringz
.idup
;
87 void openUrl (ConString url
, bool forceOpera
=false) {
89 import std
.stdio
: File
;
92 auto frd
= File("/dev/null");
93 auto fwr
= File("/dev/null", "w");
94 spawnProcess([getBrowserCommand(forceOpera
), url
.idup
], frd
, fwr
, fwr
, null, Config
.detached
);
95 } catch (Exception e
) {
96 conwriteln("ERROR executing URL viewer (", e
.msg
, ")");
102 // ////////////////////////////////////////////////////////////////////////// //
104 int pos
= -1, len
= 0;
106 @property bool valid () const pure nothrow @safe @nogc => (pos
>= 0 && len
> 0);
107 @property int end () const pure nothrow @safe @nogc => (pos
>= 0 && len
> 0 ? pos
+len
: 0);
109 static UrlInfo
Invalid () pure nothrow @safe @nogc => UrlInfo
.init
;
113 UrlInfo
urlDetect (const(char)[] text
) nothrow @trusted @nogc {
116 auto dlpos
= text
.indexOf("://");
117 if (dlpos
< 3) return res
;
119 //{ import core.stdc.stdio; printf("det: <%.*s>\n", cast(uint)text.length, text.ptr); }
121 bool isProto (const(char)[] prt
) nothrow @trusted @nogc {
122 if (dlpos
< prt
.length
) return false;
123 if (!strEquCI(prt
, text
[dlpos
-prt
.length
..dlpos
])) return false;
124 // check word boundary
125 if (dlpos
== prt
.length
) return true;
126 return !isalpha(text
[dlpos
-prt
.length
-1]);
129 if (isProto("ftp")) res
.pos
= cast(int)(dlpos
-3);
130 else if (isProto("http")) res
.pos
= cast(int)(dlpos
-4);
131 else if (isProto("https")) res
.pos
= cast(int)(dlpos
-5);
134 dlpos
+= 3; // skip "://"
137 for (; dlpos
< text
.length
; ++dlpos
) {
138 char ch
= text
[dlpos
];
139 if (ch
== '/') break;
140 if (!(isalnum(ch
) || ch
== '.' || ch
== '-' || ch
== ':' || ch
== '@')) break;
146 bool wasSharp
= false;
148 for (; dlpos
< text
.length
; ++dlpos
) {
149 char ch
= text
[dlpos
];
150 if (ch
<= ' ' || ch
== '<' || ch
== '>' || ch
== '"' || ch
== '\'' || ch
>= 127) break;
164 if (ch
== '(' || ch
== '[' || ch
== '{') {
165 if (dlpos
+1 >= text
.length ||
(!isalnum(text
[dlpos
+1]) && text
[dlpos
+1] != '_')) break;
167 case '(': ch
= ')'; break;
168 case '[': ch
= ']'; break;
169 case '{': ch
= '}'; break;
171 if (brcSP
< brcStack
.length
) brcStack
[brcSP
++] = ch
;
175 if (ch
== ')' || ch
== ']' || ch
== '}') {
176 if (brcSP
== 0 || brcStack
[brcSP
-1] != ch
) break;
183 if (dlpos
+1 >= text
.length ||
(!isalnum(text
[dlpos
+1]) && text
[dlpos
+1] != '_')) break;
188 // other special chars
189 if (dlpos
+1 >= text
.length
) break; // no more chars, ignore
190 if (ch
== '-' && dlpos
< text
.length
&& (isalnum(text
[dlpos
+1]) || text
[dlpos
+1] == '%')) continue;
191 if (!isalnum(text
[dlpos
+1]) && text
[dlpos
+1] != '_') {
192 if (ch
== '.' || ch
== '!' || ch
== ';' || ch
== ',' || text
[dlpos
+1] != ch
) break; // ignore
199 res
.len
= cast(int)(dlpos
-res
.pos
);
205 // ////////////////////////////////////////////////////////////////////////// //
206 __gshared NVGContext nvg
= null;
207 __gshared NVGImage nvgSkullsImg
;
208 __gshared NVGImage kittyOut
, kittyMsg
, kittyFish
;
209 __gshared NVGImage
[5] statusImgId
;
211 __gshared
int lastWindowWidth
= -1;
214 shared static ~this () {
215 //{ import core.stdc.stdio; printf("******************************\n"); }
216 nvgSkullsImg
.clear();
217 //{ import core.stdc.stdio; printf("---\n"); }
218 foreach (ref img
; statusImgId
[]) img
.clear();
225 void buildStatusImages () {
227 statusImgId
[ContactStatus
.Offline
] = nvg
.createImageRGBA(16, 16, ctiOffline
[], NVGImageFlags
.NoFiltering
);
228 statusImgId
[ContactStatus
.Online
] = nvg
.createImageRGBA(16, 16, ctiOnline
[], NVGImageFlags
.NoFiltering
);
229 statusImgId
[ContactStatus
.Away
] = nvg
.createImageRGBA(16, 16, ctiAway
[], NVGImageFlags
.NoFiltering
);
230 statusImgId
[ContactStatus
.Connecting
] = nvg
.createImageRGBA(16, 16, ctiOffline
[], NVGImageFlags
.NoFiltering
);
232 //{ import core.stdc.stdio; printf("creating status image: Offline\n"); }
233 statusImgId
[ContactStatus
.Offline
] = nvg
.createImageRGBA(16, 16, baph16Gray
[], NVGImageFlags
.NoFiltering
);
234 //{ import core.stdc.stdio; printf("creating status image: Online\n"); }
235 statusImgId
[ContactStatus
.Online
] = nvg
.createImageRGBA(16, 16, baph16Online
[], NVGImageFlags
.NoFiltering
);
236 //{ import core.stdc.stdio; printf("creating status image: Away\n"); }
237 statusImgId
[ContactStatus
.Away
] = nvg
.createImageRGBA(16, 16, baph16Away
[], NVGImageFlags
.NoFiltering
);
238 //{ import core.stdc.stdio; printf("creating status image: Busy\n"); }
239 statusImgId
[ContactStatus
.Busy
] = nvg
.createImageRGBA(16, 16, baph16Busy
[], NVGImageFlags
.NoFiltering
);
240 //{ import core.stdc.stdio; printf("creating status image: Connecting\n"); }
241 statusImgId
[ContactStatus
.Connecting
] = nvg
.createImageRGBA(16, 16, baph16Orange
[], NVGImageFlags
.NoFiltering
);
242 //{ import core.stdc.stdio; printf("+++ creatied status images...\n"); }
243 kittyOut
= nvg
.createImageRGBA(16, 16, kittyOutgoing
[], NVGImageFlags
.NoFiltering
);
244 kittyMsg
= nvg
.createImageRGBA(16, 16, kittyMessage
[], NVGImageFlags
.NoFiltering
);
245 kittyFish
= nvg
.createImageRGBA(16, 16, kittyFish16
[], NVGImageFlags
.NoFiltering
);
250 // ////////////////////////////////////////////////////////////////////////// //
252 nvg
.fonsContext
.addFontsFrom(fstash
);
253 bndSetFont(nvg
.findFont("ui"));
257 // ////////////////////////////////////////////////////////////////////////// //
258 void loadAccount (string nick
, bool allowCreate
) {
259 if (nick
.length
== 0) assert(0, "wtf?!");
265 acc
= new Account(nick
);
266 } catch (Exception e
) {
267 conwriteln("error opening account... (error: ", e
.msg
, ")");
268 if (!allowCreate
) throw e
;
269 acc
= Account
.CreateNew(nick
, nick
);
273 // create fake contact
274 if (newAcc
&& acc
.contacts
.length
== 0 && nick
== "_fakeacc") {
275 conwriteln("creating fake contact...");
276 auto c
= acc
.createEmptyContact();
277 c
.info
.nick
= "test contact";
278 c
.info
.pubkey
[] = 0x55;
282 clist
.buildAccount(acc
);
286 // ////////////////////////////////////////////////////////////////////////// //
288 void doActivateContact (Contact ct
) {
289 if (sdmain
is null || sdmain
.closed
) return;
290 if (activeContact
is ct
) return;
295 //conwriteln("clear log");
296 if (clist
!is null) clist
.resetActiveItem();
297 sdmain
.title
= "BioAcid";
298 glconPostScreenRepaint();
299 } else if (ct
.acceptPending
) {
300 addTextToLog(ct
.acc
, ct
, LogFile
.Msg
.Kind
.Notification
, false, (ct
.statusmsg
.length ? ct
.statusmsg
: "I brought you a tasty fish!"), systimeNow
);
302 import std
.format
: format
;
303 sdmain
.title
= "%s [%s] -- BioAcid".format(ct
.info
.nick
, tox_hex(ct
.info
.pubkey
));
306 auto mcount
= cast(int)log
.messages
.length
;
307 int left
= ct
.hmcOnOpen
;
308 if (left
< ct
.unreadCount
) left
= ct
.unreadCount
;
309 if (left
> mcount
) left
= mcount
;
310 if (mcount
> left
) mcount
= left
;
312 foreach (const ref msg
; log
.messages
[$-mcount
..$]) {
313 if (left
== ct
.unreadCount
) addDividerLine();
314 addTextToLog(ct
.acc
, ct
, msg
);
318 if (ct
.unreadCount
!= 0) { ct
.unreadCount
= 0; ct
.saveUnreadCount(); }
325 //FIXME: scan all accounts
326 void fixTrayIcon () {
327 if (clist
is null) return;
328 auto acc
= clist
.mainAccount
;
329 if (acc
is null) return;
331 foreach (Contact ct
; acc
) unc
+= ct
.unreadCount
;
333 import std
.format
: format
;
335 setHint("unread: %d".format(unc
));
337 setTrayStatus(acc
.status
);
338 final switch (acc
.status
) {
339 case ContactStatus
.Connecting
: setHint("connecting..."); break;
340 case ContactStatus
.Offline
: setHint("offline"); break;
341 case ContactStatus
.Online
: setHint("online"); break;
342 case ContactStatus
.Away
: setHint("away"); break;
343 case ContactStatus
.Busy
: setHint("busy"); break;
349 void fixUnreadIndicators () {
350 if (!mainWindowVisible ||
!mainWindowActive
) return; // nothing to do
351 if (activeContact
is null || activeContact
.unreadCount
== 0) return; // nothing to do
352 activeContact
.unreadCount
= 0;
353 activeContact
.unreadCount
= 0;
354 activeContact
.saveUnreadCount();
359 // ////////////////////////////////////////////////////////////////////////// //
360 void addContactCommands () {
361 conRegFunc
!((ConString grpname
) {
362 if (clist
is null || sdmain
is null || sdmain
.closed
) return;
363 auto acc
= clist
.mainAccount
;
364 if (acc
is null) return;
365 if (grpname
.length
== 0) { conwriteln("group name?"); return; }
366 if (acc
.findGroupByName(grpname
) != uint.max
) { conwriteln("group <", grpname
, "> already exists"); return; }
367 auto gid
= acc
.createGroup(grpname
);
368 if (gid
== uint.max
) { conwriteln("cannot create group <", grpname
, ">"); return; }
369 clist
.buildAccount(acc
);
370 glconPostScreenRepaint();
371 })("group_create", "create group");
374 conRegFunc
!((ConString grpname
) {
375 if (clist
is null || sdmain
is null || sdmain
.closed
) return;
376 if (activeContact
is null) { conwriteln("please, select contact first"); return; }
377 if (grpname
.length
== 0) { conwriteln("group name?"); return; }
378 auto gid
= activeContact
.acc
.createGroup(grpname
);
379 if (gid
== uint.max
) { conwriteln("cannot create group <", grpname
, ">"); return; }
380 if (activeContact
.acc
.moveContactToGroup(activeContact
, gid
)) {
381 clist
.buildAccount(activeContact
.acc
);
382 glconPostScreenRepaint();
384 conwriteln("cannot move contact to new group");
386 })("move_to_group", "move current contact to named group");
390 if (clist
is null || sdmain
is null || sdmain
.closed
) return;
391 if (activeContact
is null) { conwriteln("please, select contact first"); return; }
392 auto acc
= activeContact
.acc
;
393 if (!acc
.isOnline
) { conwriteln("you must be online to remove contacts"); return; }
394 if (!acc
.removeContact(activeContact
)) { conwriteln("cannot remove current contact"); return; }
396 activeContact
= null;
397 clist
.buildAccount(acc
);
398 glconPostScreenRepaint();
399 })("contact_remove", "unfriend and remove current contact");