Mercurial > ~mikael > mcabber > hg
comparison mcabber/mcabber/hbuf.c @ 1668:41c26b7d2890
Install mcabber headers
* Change mcabber headers naming scheme
* Move 'src/' -> 'mcabber/'
* Add missing include <mcabber/config.h>'s
* Create and install clean config.h version in 'include/'
* Move "dirty" config.h version to 'mcabber/'
* Add $(top_srcdir) to compiler include path
* Update modules HOWTO
author | Myhailo Danylenko <isbear@ukrpost.net> |
---|---|
date | Mon, 18 Jan 2010 15:36:19 +0200 |
parents | mcabber/src/hbuf.c@f4a2c6f767d1 |
children | e6e89b1d7831 |
comparison
equal
deleted
inserted
replaced
1667:8af0e0ad20ad | 1668:41c26b7d2890 |
---|---|
1 /* | |
2 * hbuf.c -- History buffer implementation | |
3 * | |
4 * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net> | |
5 * | |
6 * This program is free software; you can redistribute it and/or modify | |
7 * it under the terms of the GNU General Public License as published by | |
8 * the Free Software Foundation; either version 2 of the License, or (at | |
9 * your option) any later version. | |
10 * | |
11 * This program is distributed in the hope that it will be useful, but | |
12 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 * General Public License for more details. | |
15 * | |
16 * You should have received a copy of the GNU General Public License | |
17 * along with this program; if not, write to the Free Software | |
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 | |
19 * USA | |
20 */ | |
21 | |
22 #include <string.h> | |
23 #include <sys/types.h> | |
24 #include <sys/stat.h> | |
25 #include <unistd.h> | |
26 | |
27 #include "hbuf.h" | |
28 #include "utils.h" | |
29 #include "utf8.h" | |
30 #include "screen.h" | |
31 | |
32 | |
33 /* This is a private structure type */ | |
34 | |
35 typedef struct { | |
36 char *ptr; | |
37 char *ptr_end; // beginning of the block | |
38 char *ptr_end_alloc; // end of the current persistent block | |
39 guchar flags; | |
40 | |
41 // XXX This should certainly be a pointer, and be allocated only when needed | |
42 // (for ex. when HBB_FLAG_PERSISTENT is set). | |
43 struct { // hbuf_line_info | |
44 time_t timestamp; | |
45 unsigned mucnicklen; | |
46 guint flags; | |
47 gpointer xep184; | |
48 } prefix; | |
49 } hbuf_block; | |
50 | |
51 | |
52 // do_wrap(p_hbuf, first_hbuf_elt, width) | |
53 // Wrap hbuf lines with the specified width. | |
54 // '\n' are handled by this routine (they are removed and persistent lines | |
55 // are created). | |
56 // All hbuf elements are processed, starting from first_hbuf_elt. | |
57 static inline void do_wrap(GList **p_hbuf, GList *first_hbuf_elt, | |
58 unsigned int width) | |
59 { | |
60 GList *curr_elt = first_hbuf_elt; | |
61 | |
62 // Let's add non-persistent blocs if necessary | |
63 // - If there are '\n' in the string | |
64 // - If length > width (and width != 0) | |
65 while (curr_elt) { | |
66 hbuf_block *hbuf_b_curr, *hbuf_b_prev; | |
67 char *c, *end; | |
68 char *br = NULL; // break pointer | |
69 char *cr = NULL; // CR pointer | |
70 unsigned int cur_w = 0; | |
71 | |
72 // We want to break where we can find a space char or a CR | |
73 | |
74 hbuf_b_curr = (hbuf_block*)(curr_elt->data); | |
75 hbuf_b_prev = hbuf_b_curr; | |
76 c = hbuf_b_curr->ptr; | |
77 | |
78 while (*c && (!width || cur_w <= width)) { | |
79 if (*c == '\n') { | |
80 br = cr = c; | |
81 *c = 0; | |
82 break; | |
83 } | |
84 if (iswblank(get_char(c))) | |
85 br = c; | |
86 cur_w += get_char_width(c); | |
87 c = next_char(c); | |
88 } | |
89 | |
90 if (cr || (*c && cur_w > width)) { | |
91 if (!br || br == hbuf_b_curr->ptr) | |
92 br = c; | |
93 else | |
94 br = next_char(br); | |
95 end = hbuf_b_curr->ptr_end; | |
96 hbuf_b_curr->ptr_end = br; | |
97 // Create another block | |
98 hbuf_b_curr = g_new0(hbuf_block, 1); | |
99 // The block must be persistent after a CR | |
100 if (cr) { | |
101 hbuf_b_curr->ptr = hbuf_b_prev->ptr_end + 1; // == cr+1 | |
102 hbuf_b_curr->flags = HBB_FLAG_PERSISTENT; | |
103 } else { | |
104 hbuf_b_curr->ptr = hbuf_b_prev->ptr_end; // == br | |
105 hbuf_b_curr->flags = 0; | |
106 } | |
107 hbuf_b_curr->ptr_end = end; | |
108 hbuf_b_curr->ptr_end_alloc = hbuf_b_prev->ptr_end_alloc; | |
109 // This is OK because insert_before(NULL) == append(): | |
110 *p_hbuf = g_list_insert_before(*p_hbuf, curr_elt->next, hbuf_b_curr); | |
111 } | |
112 curr_elt = g_list_next(curr_elt); | |
113 } | |
114 } | |
115 | |
116 // hbuf_add_line(p_hbuf, text, prefix_flags, width, maxhbufblocks) | |
117 // Add a line to the given buffer. If width is not null, then lines are | |
118 // wrapped at this length. | |
119 // maxhbufblocks is the maximum number of hbuf blocks we can allocate. If | |
120 // null, there is no limit. If non-null, it should be >= 2. | |
121 // | |
122 // Note 1: Splitting according to width won't work if there are tabs; they | |
123 // should be expanded before. | |
124 // Note 2: width does not include the ending \0. | |
125 void hbuf_add_line(GList **p_hbuf, const char *text, time_t timestamp, | |
126 guint prefix_flags, guint width, guint maxhbufblocks, | |
127 unsigned mucnicklen, gpointer xep184) | |
128 { | |
129 GList *curr_elt; | |
130 char *line; | |
131 guint hbb_blocksize, textlen; | |
132 hbuf_block *hbuf_block_elt; | |
133 | |
134 if (!text) return; | |
135 | |
136 prefix_flags |= (xep184 ? HBB_PREFIX_RECEIPT : 0); | |
137 | |
138 textlen = strlen(text); | |
139 hbb_blocksize = MAX(textlen+1, HBB_BLOCKSIZE); | |
140 | |
141 hbuf_block_elt = g_new0(hbuf_block, 1); | |
142 hbuf_block_elt->prefix.timestamp = timestamp; | |
143 hbuf_block_elt->prefix.flags = prefix_flags; | |
144 hbuf_block_elt->prefix.mucnicklen = mucnicklen; | |
145 hbuf_block_elt->prefix.xep184 = xep184; | |
146 if (!*p_hbuf) { | |
147 hbuf_block_elt->ptr = g_new(char, hbb_blocksize); | |
148 if (!hbuf_block_elt->ptr) { | |
149 g_free(hbuf_block_elt); | |
150 return; | |
151 } | |
152 hbuf_block_elt->flags = HBB_FLAG_ALLOC | HBB_FLAG_PERSISTENT; | |
153 hbuf_block_elt->ptr_end_alloc = hbuf_block_elt->ptr + hbb_blocksize; | |
154 } else { | |
155 hbuf_block *hbuf_b_prev; | |
156 // Set p_hbuf to the end of the list, to speed up history loading | |
157 // (or CPU time will be used by g_list_last() for each line) | |
158 *p_hbuf = g_list_last(*p_hbuf); | |
159 hbuf_b_prev = (*p_hbuf)->data; | |
160 hbuf_block_elt->ptr = hbuf_b_prev->ptr_end; | |
161 hbuf_block_elt->flags = HBB_FLAG_PERSISTENT; | |
162 hbuf_block_elt->ptr_end_alloc = hbuf_b_prev->ptr_end_alloc; | |
163 } | |
164 *p_hbuf = g_list_append(*p_hbuf, hbuf_block_elt); | |
165 | |
166 if (hbuf_block_elt->ptr + textlen >= hbuf_block_elt->ptr_end_alloc) { | |
167 // Too long for the current allocated bloc, we need another one | |
168 if (!maxhbufblocks || textlen >= HBB_BLOCKSIZE) { | |
169 // No limit, let's allocate a new block | |
170 // If the message text is big, we won't bother to reuse an old block | |
171 // as well (it could be too small and cause a segfault). | |
172 hbuf_block_elt->ptr = g_new0(char, hbb_blocksize); | |
173 hbuf_block_elt->ptr_end_alloc = hbuf_block_elt->ptr + hbb_blocksize; | |
174 // XXX We should check the return value. | |
175 } else { | |
176 GList *hbuf_head, *hbuf_elt; | |
177 hbuf_block *hbuf_b_elt; | |
178 guint n = 0; | |
179 hbuf_head = g_list_first(*p_hbuf); | |
180 // We need at least 2 allocated blocks | |
181 if (maxhbufblocks == 1) | |
182 maxhbufblocks = 2; | |
183 // Let's count the number of allocated areas | |
184 for (hbuf_elt = hbuf_head; hbuf_elt; hbuf_elt = g_list_next(hbuf_elt)) { | |
185 hbuf_b_elt = (hbuf_block*)(hbuf_elt->data); | |
186 if (hbuf_b_elt->flags & HBB_FLAG_ALLOC) | |
187 n++; | |
188 } | |
189 // If we can't allocate a new area, reuse the previous block(s) | |
190 if (n < maxhbufblocks) { | |
191 hbuf_block_elt->ptr = g_new0(char, hbb_blocksize); | |
192 hbuf_block_elt->ptr_end_alloc = hbuf_block_elt->ptr + hbb_blocksize; | |
193 } else { | |
194 // Let's use an old block, and free the extra blocks if needed | |
195 char *allocated_block = NULL; | |
196 char *end_of_allocated_block = NULL; | |
197 while (n >= maxhbufblocks) { | |
198 int start_of_block = 1; | |
199 for (hbuf_elt = hbuf_head; hbuf_elt; hbuf_elt = hbuf_head) { | |
200 hbuf_b_elt = (hbuf_block*)(hbuf_elt->data); | |
201 if (hbuf_b_elt->flags & HBB_FLAG_ALLOC) { | |
202 if (start_of_block-- == 0) | |
203 break; | |
204 if (n == maxhbufblocks) { | |
205 allocated_block = hbuf_b_elt->ptr; | |
206 end_of_allocated_block = hbuf_b_elt->ptr_end_alloc; | |
207 } else { | |
208 g_free(hbuf_b_elt->ptr); | |
209 } | |
210 } | |
211 g_free(hbuf_b_elt); | |
212 hbuf_head = *p_hbuf = g_list_delete_link(hbuf_head, hbuf_elt); | |
213 } | |
214 n--; | |
215 } | |
216 memset(allocated_block, 0, end_of_allocated_block-allocated_block); | |
217 hbuf_block_elt->ptr = allocated_block; | |
218 hbuf_block_elt->ptr_end_alloc = end_of_allocated_block; | |
219 } | |
220 } | |
221 hbuf_block_elt->flags = HBB_FLAG_ALLOC | HBB_FLAG_PERSISTENT; | |
222 } | |
223 | |
224 line = hbuf_block_elt->ptr; | |
225 // Ok, now we can copy the text.. | |
226 strcpy(line, text); | |
227 hbuf_block_elt->ptr_end = line + textlen + 1; | |
228 | |
229 curr_elt = g_list_last(*p_hbuf); | |
230 | |
231 // Wrap lines and handle CRs ('\n') | |
232 do_wrap(p_hbuf, curr_elt, width); | |
233 } | |
234 | |
235 // hbuf_free() | |
236 // Destroys all hbuf list. | |
237 void hbuf_free(GList **p_hbuf) | |
238 { | |
239 hbuf_block *hbuf_b_elt; | |
240 GList *hbuf_elt; | |
241 GList *first_elt = g_list_first(*p_hbuf); | |
242 | |
243 for (hbuf_elt = first_elt; hbuf_elt; hbuf_elt = g_list_next(hbuf_elt)) { | |
244 hbuf_b_elt = (hbuf_block*)(hbuf_elt->data); | |
245 if (hbuf_b_elt->flags & HBB_FLAG_ALLOC) { | |
246 g_free(hbuf_b_elt->ptr); | |
247 } | |
248 g_free(hbuf_b_elt); | |
249 } | |
250 | |
251 g_list_free(first_elt); | |
252 *p_hbuf = NULL; | |
253 } | |
254 | |
255 // hbuf_rebuild() | |
256 // Rebuild all hbuf list, with the new width. | |
257 // If width == 0, lines are not wrapped. | |
258 void hbuf_rebuild(GList **p_hbuf, unsigned int width) | |
259 { | |
260 GList *first_elt, *curr_elt, *next_elt; | |
261 hbuf_block *hbuf_b_curr, *hbuf_b_next; | |
262 | |
263 // *p_hbuf needs to be the head of the list | |
264 first_elt = *p_hbuf = g_list_first(*p_hbuf); | |
265 | |
266 // #1 Remove non-persistent blocks (ptr_end should be updated!) | |
267 curr_elt = first_elt; | |
268 while (curr_elt) { | |
269 next_elt = g_list_next(curr_elt); | |
270 // Last element? | |
271 if (!next_elt) | |
272 break; | |
273 hbuf_b_curr = (hbuf_block*)(curr_elt->data); | |
274 hbuf_b_next = (hbuf_block*)(next_elt->data); | |
275 // Is next line not-persistent? | |
276 if (!(hbuf_b_next->flags & HBB_FLAG_PERSISTENT)) { | |
277 hbuf_b_curr->ptr_end = hbuf_b_next->ptr_end; | |
278 g_free(hbuf_b_next); | |
279 curr_elt = g_list_delete_link(curr_elt, next_elt); | |
280 } else | |
281 curr_elt = next_elt; | |
282 } | |
283 // #2 Go back to head and create non-persistent blocks when needed | |
284 if (width) | |
285 do_wrap(p_hbuf, first_elt, width); | |
286 } | |
287 | |
288 // hbuf_previous_persistent() | |
289 // Returns the previous persistent block (line). If the given line is | |
290 // persistent, then it is returned. | |
291 // This function is used for example when resizing a buffer. If the top of the | |
292 // screen is on a non-persistent block, then a screen resize could destroy this | |
293 // line... | |
294 GList *hbuf_previous_persistent(GList *l_line) | |
295 { | |
296 hbuf_block *hbuf_b_elt; | |
297 | |
298 while (l_line) { | |
299 hbuf_b_elt = (hbuf_block*)l_line->data; | |
300 if (hbuf_b_elt->flags & HBB_FLAG_PERSISTENT) | |
301 return l_line; | |
302 l_line = g_list_previous(l_line); | |
303 } | |
304 | |
305 return NULL; | |
306 } | |
307 | |
308 // hbuf_get_lines(hbuf, n) | |
309 // Returns an array of n hbb_line pointers | |
310 // (The first line will be the line currently pointed by hbuf) | |
311 // Note: The caller should free the array, the hbb_line pointers and the | |
312 // text pointers after use. | |
313 hbb_line **hbuf_get_lines(GList *hbuf, unsigned int n) | |
314 { | |
315 unsigned int i; | |
316 hbuf_block *blk; | |
317 guint last_persist_prefixflags = 0; | |
318 GList *last_persist; // last persistent flags | |
319 hbb_line **array, **array_elt; | |
320 | |
321 // To be able to correctly highlight multi-line messages, | |
322 // we need to look at the last non-null prefix, which should be the first | |
323 // line of the message. | |
324 last_persist = hbuf_previous_persistent(hbuf); | |
325 while (last_persist) { | |
326 blk = (hbuf_block*)last_persist->data; | |
327 if ((blk->flags & HBB_FLAG_PERSISTENT) && blk->prefix.flags) { | |
328 last_persist_prefixflags = blk->prefix.flags; | |
329 break; | |
330 } | |
331 last_persist = g_list_previous(last_persist); | |
332 } | |
333 | |
334 array = g_new0(hbb_line*, n); | |
335 array_elt = array; | |
336 | |
337 for (i = 0 ; i < n ; i++) { | |
338 if (hbuf) { | |
339 int maxlen; | |
340 | |
341 blk = (hbuf_block*)(hbuf->data); | |
342 maxlen = blk->ptr_end - blk->ptr; | |
343 *array_elt = (hbb_line*)g_new(hbb_line, 1); | |
344 (*array_elt)->timestamp = blk->prefix.timestamp; | |
345 (*array_elt)->flags = blk->prefix.flags; | |
346 (*array_elt)->mucnicklen = blk->prefix.mucnicklen; | |
347 (*array_elt)->text = g_strndup(blk->ptr, maxlen); | |
348 | |
349 if ((blk->flags & HBB_FLAG_PERSISTENT) && blk->prefix.flags) { | |
350 last_persist_prefixflags = blk->prefix.flags; | |
351 } else { | |
352 // Propagate highlighting flags | |
353 (*array_elt)->flags |= last_persist_prefixflags & | |
354 (HBB_PREFIX_HLIGHT_OUT | HBB_PREFIX_HLIGHT | | |
355 HBB_PREFIX_INFO | HBB_PREFIX_IN); | |
356 // Continuation of a message - omit the prefix | |
357 (*array_elt)->flags |= HBB_PREFIX_CONT; | |
358 (*array_elt)->mucnicklen = 0; // The nick is in the first one | |
359 } | |
360 | |
361 hbuf = g_list_next(hbuf); | |
362 } else | |
363 break; | |
364 | |
365 array_elt++; | |
366 } | |
367 | |
368 return array; | |
369 } | |
370 | |
371 // hbuf_search(hbuf, direction, string) | |
372 // Look backward/forward for a line containing string in the history buffer | |
373 // Search starts at hbuf, and goes forward if direction == 1, backward if -1 | |
374 GList *hbuf_search(GList *hbuf, int direction, const char *string) | |
375 { | |
376 hbuf_block *blk; | |
377 | |
378 for (;;) { | |
379 if (direction > 0) | |
380 hbuf = g_list_next(hbuf); | |
381 else | |
382 hbuf = g_list_previous(hbuf); | |
383 | |
384 if (!hbuf) break; | |
385 | |
386 blk = (hbuf_block*)(hbuf->data); | |
387 // XXX blk->ptr is (maybe) not really correct, because the match should | |
388 // not be after ptr_end. We should check that... | |
389 if (strcasestr(blk->ptr, string)) | |
390 break; | |
391 } | |
392 | |
393 return hbuf; | |
394 } | |
395 | |
396 // hbuf_jump_date(hbuf, t) | |
397 // Return a pointer to the first line after date t in the history buffer | |
398 GList *hbuf_jump_date(GList *hbuf, time_t t) | |
399 { | |
400 hbuf_block *blk; | |
401 | |
402 hbuf = g_list_first(hbuf); | |
403 | |
404 for ( ; hbuf && g_list_next(hbuf); hbuf = g_list_next(hbuf)) { | |
405 blk = (hbuf_block*)(hbuf->data); | |
406 if (blk->prefix.timestamp >= t) break; | |
407 } | |
408 | |
409 return hbuf; | |
410 } | |
411 | |
412 // hbuf_jump_percent(hbuf, pc) | |
413 // Return a pointer to the line at % pc of the history buffer | |
414 GList *hbuf_jump_percent(GList *hbuf, int pc) | |
415 { | |
416 guint hlen; | |
417 | |
418 hbuf = g_list_first(hbuf); | |
419 hlen = g_list_length(hbuf); | |
420 | |
421 return g_list_nth(hbuf, pc*hlen/100); | |
422 } | |
423 | |
424 // hbuf_dump_to_file(hbuf, filename) | |
425 // Save the buffer to a file. | |
426 void hbuf_dump_to_file(GList *hbuf, const char *filename) | |
427 { | |
428 hbuf_block *blk; | |
429 hbb_line line; | |
430 guint last_persist_prefixflags = 0; | |
431 guint prefixwidth; | |
432 char pref[96]; | |
433 FILE *fp; | |
434 struct stat statbuf; | |
435 | |
436 if (!stat(filename, &statbuf)) { | |
437 scr_LogPrint(LPRINT_NORMAL, "The file already exists."); | |
438 return; | |
439 } | |
440 fp = fopen(filename, "w"); | |
441 if (!fp) { | |
442 scr_LogPrint(LPRINT_NORMAL, "Unable to open the file."); | |
443 return; | |
444 } | |
445 | |
446 prefixwidth = scr_getprefixwidth(); | |
447 prefixwidth = MIN(prefixwidth, sizeof pref); | |
448 | |
449 for (hbuf = g_list_first(hbuf); hbuf; hbuf = g_list_next(hbuf)) { | |
450 int maxlen; | |
451 | |
452 blk = (hbuf_block*)(hbuf->data); | |
453 maxlen = blk->ptr_end - blk->ptr; | |
454 | |
455 memset(&line, 0, sizeof(line)); | |
456 line.timestamp = blk->prefix.timestamp; | |
457 line.flags = blk->prefix.flags; | |
458 line.mucnicklen = blk->prefix.mucnicklen; | |
459 line.text = g_strndup(blk->ptr, maxlen); | |
460 | |
461 if ((blk->flags & HBB_FLAG_PERSISTENT) && blk->prefix.flags) { | |
462 last_persist_prefixflags = blk->prefix.flags; | |
463 } else { | |
464 // Propagate highlighting flags | |
465 line.flags |= last_persist_prefixflags & | |
466 (HBB_PREFIX_HLIGHT_OUT | HBB_PREFIX_HLIGHT | | |
467 HBB_PREFIX_INFO | HBB_PREFIX_IN); | |
468 // Continuation of a message - omit the prefix | |
469 line.flags |= HBB_PREFIX_CONT; | |
470 line.mucnicklen = 0; // The nick is in the first one | |
471 } | |
472 | |
473 scr_line_prefix(&line, pref, prefixwidth); | |
474 fprintf(fp, "%s%s\n", pref, line.text); | |
475 } | |
476 | |
477 fclose(fp); | |
478 return; | |
479 } | |
480 | |
481 // hbuf_remove_receipt(hbuf, xep184) | |
482 // Remove the Receipt Flag for the message with the given xep184 id | |
483 // Returns TRUE if it was found and removed, otherwise FALSE | |
484 gboolean hbuf_remove_receipt(GList *hbuf, gpointer xep184) | |
485 { | |
486 hbuf_block *blk; | |
487 | |
488 hbuf = g_list_first(hbuf); | |
489 | |
490 for ( ; hbuf; hbuf = g_list_next(hbuf)) { | |
491 blk = (hbuf_block*)(hbuf->data); | |
492 if (blk->prefix.xep184 == xep184) { | |
493 blk->prefix.xep184 = NULL; | |
494 blk->prefix.flags ^= HBB_PREFIX_RECEIPT; | |
495 return TRUE; | |
496 } | |
497 } | |
498 return FALSE; | |
499 } | |
500 | |
501 // hbuf_get_blocks_number() | |
502 // Returns the number of allocated hbuf_block's. | |
503 guint hbuf_get_blocks_number(GList *hbuf) | |
504 { | |
505 hbuf_block *hbuf_b_elt; | |
506 guint count = 0U; | |
507 | |
508 for (hbuf = g_list_first(hbuf); hbuf; hbuf = g_list_next(hbuf)) { | |
509 hbuf_b_elt = (hbuf_block*)(hbuf->data); | |
510 if (hbuf_b_elt->flags & HBB_FLAG_ALLOC) | |
511 count++; | |
512 } | |
513 return count; | |
514 } | |
515 | |
516 /* vim: set expandtab cindent cinoptions=>2\:2(0: For Vim users... */ |