1 
2 /******************************************************************************
3 * MODULE     : new_view.cpp
4 * DESCRIPTION: View management
5 * COPYRIGHT  : (C) 1999-2012  Joris van der Hoeven
6 *******************************************************************************
7 * This software falls under the GNU general public license version 3 or later.
8 * It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE
9 * in the root directory or <http://www.gnu.org/licenses/gpl-3.0.html>.
10 ******************************************************************************/
11 
12 #include "tm_data.hpp"
13 #include "convert.hpp"
14 #include "file.hpp"
15 #include "web_files.hpp"
16 #include "tm_link.hpp"
17 #include "message.hpp"
18 #include "dictionary.hpp"
19 #include "new_document.hpp"
20 #include "drd_std.hpp"
21 
22 #if defined(OS_WIN32)  || defined(__MINGW32__)
23 #define WINPATHS
24 #endif
25 
26 /******************************************************************************
27 * Associating URLs to views
28 ******************************************************************************/
29 
30 static hashmap<tree,int> view_number_table (0);
31 static hashmap<tree,pointer> view_table (NULL);
32 
33 static int
new_view_number(url u)34 new_view_number (url u) {
35   view_number_table (u->t) += 1;
36   return view_number_table [u->t];
37 }
38 
tm_view_rep(tm_buffer buf2,editor ed2)39 tm_view_rep::tm_view_rep (tm_buffer buf2, editor ed2):
40   buf (buf2), ed (ed2), win (NULL), nr (new_view_number (buf->buf->name)) {}
41 
42 static string
encode_url(url u)43 encode_url (url u) {
44   if (!is_rooted (u)) return "here/" * as_string (u, URL_UNIX);
45   if (get_root (u) == "default") return "default" * as_string (u, URL_UNIX);
46   return get_root (u) * "/" * as_string (unroot (u), URL_UNIX);
47 }
48 
49 static url
decode_url(string s)50 decode_url (string s) {
51   int i= search_forwards ("/", 0, s);
52   if (i < 0) return url_none ();
53 #ifdef WINPATHS
54   int j= 0;
55   if (s (0, i) == "here") j= i+1;
56   if (s (0, i) == "default") j= i;
57   if (j != 0) {
58     if (s[j+1] == ':') return url (s (j, N(s)));
59     else return url_root ("default") * url (s (j, N(s)));
60   }
61 #else
62   if (s (0, i) == "here") return url (s (i+1, N(s)));
63   if (s (0, i) == "default") return url (s (i, N(s)));
64 #endif
65   return url_root (s (0, i)) * url (s (i+1, N(s)));
66 }
67 
68 url
abstract_view(tm_view vw)69 abstract_view (tm_view vw) {
70   if (vw == NULL) return url_none ();
71   string name= encode_url (vw->buf->buf->name);
72   //cout << vw->buf->buf->name << " -> " << name << "\n";
73   string nr  = as_string (vw->nr);
74   return "tmfs://view/" * nr * "/" * name;
75 }
76 
77 tm_view
concrete_view(url u)78 concrete_view (url u) {
79   if (is_none (u)) return NULL;
80   string s= as_string (u);
81   if (!starts (s, "tmfs://view/")) return NULL;
82   s= s (N (string ("tmfs://view/")), N(s));
83   int i= search_forwards ("/", 0, s);
84   if (i < 0) return NULL;
85   int nr= as_int (s (0, i));
86   url name= decode_url (s (i+1, N(s)));
87   //cout << s (i+1, N(s)) << " -> " << name << "\n";
88   tm_buffer buf= concrete_buffer (name);
89   if (!is_nil (buf))
90     for (i=0; i<N(buf->vws); i++)
91       if (buf->vws[i]->nr == nr)
92         return buf->vws[i];
93   return NULL;
94 }
95 
96 /******************************************************************************
97 * Views associated to editor, window, or buffer
98 ******************************************************************************/
99 
100 tm_view the_view= NULL;
101 
102 bool
has_current_view()103 has_current_view () {
104   return the_view != NULL;
105 }
106 
107 void
set_current_view(url u)108 set_current_view (url u) {
109   tm_view vw= concrete_view (u);
110   //ASSERT (is_none (u) || starts (as_string (tail (u)), "no_name") || vw != NULL, "bad view");
111   the_view= vw;
112   if (vw != NULL) {
113     the_drd = vw->ed->drd;
114     vw->buf->buf->last_visit= texmacs_time ();
115   }
116 }
117 
118 url
get_current_view()119 get_current_view () {
120   ASSERT (the_view != NULL, "no active view");
121   return abstract_view (the_view);
122 }
123 
124 url
get_current_view_safe()125 get_current_view_safe () {
126   if (the_view == NULL) return url_none ();
127   return abstract_view (the_view);
128 }
129 
130 void notify_delete_view (url u);
131 
132 editor
get_current_editor()133 get_current_editor () {
134   url u= get_current_view();
135   tm_view vw= concrete_view (u);
136   if (vw == NULL) { // HACK: shouldn't happen!
137     FAILED ("Current view is NULL");
138     notify_delete_view (u);
139     array<url> history = get_all_views();
140     if (history == NULL || N(history) == 0)
141       FAILED("View history is empty")
142     return view_to_editor (history[N(history)-1]);
143   }
144   return vw->ed;
145 }
146 
147 array<url>
buffer_to_views(url name)148 buffer_to_views (url name) {
149   tm_buffer buf= concrete_buffer (name);
150   array<url> r;
151   if (is_nil (buf)) return r;
152   for (int i=0; i<N(buf->vws); i++)
153     r << abstract_view (buf->vws[i]);
154   return r;
155 }
156 
157 url
view_to_buffer(url u)158 view_to_buffer (url u) {
159   tm_view vw= concrete_view (u);
160   if (vw == NULL) return url_none ();
161   return vw->buf->buf->name;
162 }
163 
164 url
view_to_window(url u)165 view_to_window (url u) {
166   tm_view vw= concrete_view (u);
167   if (vw == NULL) return url_none ();
168   return abstract_window (vw->win);
169 }
170 
171 editor
view_to_editor(url u)172 view_to_editor (url u) {
173   tm_view vw= concrete_view (u);
174   if (vw == NULL) {
175     notify_delete_view (u); // HACK: returns to valid (?) state.
176     failed_error << "View is " << u << "\n";
177     FAILED ("View admits no editor");
178   }
179   return vw->ed;
180 }
181 
182 /******************************************************************************
183 * Viewing history
184 ******************************************************************************/
185 
186 array<url> view_history;
187 
188 void
notify_set_view(url u)189 notify_set_view (url u) {
190   int i;
191   for (i=0; i<N(view_history); i++)
192     if (view_history[i] == u) break;
193   if (i >= N(view_history))
194     view_history= append (u, view_history);
195   else {
196     int j;
197     for (j=i; j>0; j--)
198       view_history[j]= view_history[j-1];
199     view_history[j]= u;
200   }
201 }
202 
203 void
notify_delete_view(url u)204 notify_delete_view (url u) {
205   for (int i=0; i<N(view_history); i++)
206     if (view_history[i] == u) {
207       view_history= append (range (view_history, 0, i),
208 			    range (view_history, i+1, N(view_history)));
209       return;
210     }
211 }
212 
213 url
get_recent_view(url name,bool same,bool other,bool active,bool passive)214 get_recent_view (url name, bool same, bool other, bool active, bool passive) {
215   // Get most recent view with the following filters:
216   //   If same, then the name of the buffer much be name
217   //   If other, then the name of the buffer much be other than name
218   //   If active, then the buffer must be active
219   //   If passive, then the buffer must be passive
220   int i;
221   for (i= 0; i < N(view_history); i++) {
222     tm_view vw= concrete_view (view_history[i]);
223     if (vw != NULL) {
224       if (same && vw->buf->buf->name != name) continue;
225       if (other && vw->buf->buf->name == name) continue;
226       if (active && vw->win == NULL) continue;
227       if (passive && vw->win != NULL) continue;
228       return view_history[i];
229     }
230   }
231   return url_none ();
232 }
233 
234 array<url>
get_all_views()235 get_all_views () {
236   return view_history;
237 }
238 
239 /******************************************************************************
240 * Creation of views on buffers
241 ******************************************************************************/
242 
243 url tm_init_buffer_file= url_none ();
244 url my_init_buffer_file= url_none ();
245 
246 url
get_new_view(url name)247 get_new_view (url name) {
248   //cout << "Creating new view " << name << "\n";
249 
250   create_buffer (name, tree (DOCUMENT));
251   tm_buffer buf= concrete_buffer (name);
252   editor    ed = new_editor (get_server () -> get_server (), buf);
253   tm_view   vw = tm_new<tm_view_rep> (buf, ed);
254   buf->vws << vw;
255   ed->set_data (buf->data);
256 
257   url temp= get_current_view_safe ();
258   set_current_view (abstract_view (vw));
259   if (is_none (tm_init_buffer_file))
260     tm_init_buffer_file= "$TEXMACS_PATH/progs/init-buffer.scm";
261   if (is_none (my_init_buffer_file))
262     my_init_buffer_file= "$TEXMACS_HOME_PATH/progs/my-init-buffer.scm";
263   if (exists (tm_init_buffer_file)) exec_file (tm_init_buffer_file);
264   if (exists (my_init_buffer_file)) exec_file (my_init_buffer_file);
265   set_current_view (temp);
266 
267   //cout << "View created\n";
268   return abstract_view (vw);
269 }
270 
271 url
get_passive_view(url name)272 get_passive_view (url name) {
273   // Get a view on a buffer, but not one which is attached to a window
274   // Create a new view if no such view exists
275   tm_buffer buf= concrete_buffer_insist (name);
276   if (is_nil (buf)) return url_none ();
277   array<url> vs= buffer_to_views (name);
278   for (int i=0; i<N(vs); i++) {
279     url win= view_to_window (vs[i]);
280     if (is_none (win)) return vs[i];
281   }
282   return get_new_view (buf->buf->name);
283 }
284 
285 url
get_recent_view(url name)286 get_recent_view (url name) {
287   // Get (most) recent view on a buffer, with a preference for
288   // the current buffer or another view attached to a window
289   array<url> vs= buffer_to_views (name);
290   if (N(vs) == 0) return get_new_view (name);
291   url u= get_current_view ();
292   if (view_to_buffer (u) == name) return u;
293   url r= get_recent_view (name, true, false, true, false);
294   if (!is_none (r)) return r;
295   r= get_recent_view (name, true, false, false, false);
296   if (!is_none (r)) return r;
297   return vs[0];
298 }
299 
300 /******************************************************************************
301 * Destroying a view
302 ******************************************************************************/
303 
304 void
delete_view(url u)305 delete_view (url u) {
306   tm_view vw= concrete_view (u);
307   if (vw == NULL) return;
308   tm_buffer buf= vw->buf;
309   int i, j, n= N(buf->vws);
310   for (i=0; i<n; i++)
311     if (buf->vws[i] == vw) {
312       array<tm_view> a (n-1);
313       for (j=0; j<n-1; j++)
314 	if (j<i) a[j]= buf->vws[j];
315 	else a[j]= buf->vws[j+1];
316       buf->vws= a;
317     }
318   notify_delete_view (u);
319   tm_delete (vw);
320 }
321 
322 void
notify_rename_before(url old_name)323 notify_rename_before (url old_name) {
324   array<url> vs= buffer_to_views (old_name);
325   for (int i=0; i<N(vs); i++)
326     notify_delete_view (vs[i]);
327 }
328 
329 void
notify_rename_after(url new_name)330 notify_rename_after (url new_name) {
331   array<url> vs= buffer_to_views (new_name);
332   for (int i=0; i<N(vs); i++)
333     notify_set_view (vs[i]);
334 }
335 
336 /******************************************************************************
337 * Attaching and detaching views
338 ******************************************************************************/
339 
340 void
attach_view(url win_u,url u)341 attach_view (url win_u, url u) {
342   tm_window win= concrete_window (win_u);
343   tm_view   vw = concrete_view (u);
344   if (win == NULL || vw == NULL) return;
345   // cout << "Attach view " << vw->buf->buf->name << "\n";
346   vw->win= win;
347   widget wid= win->wid;
348   set_scrollable (wid, vw->ed);
349   vw->ed->cvw= wid.rep;
350   ASSERT (is_attached (wid), "widget should be attached");
351   vw->ed->resume ();
352   win->set_window_name (vw->buf->buf->title);
353   win->set_window_url (vw->buf->buf->name);
354   notify_set_view (u);
355   // cout << "View attached\n";
356 }
357 
358 void
detach_view(url u)359 detach_view (url u) {
360   tm_view vw = concrete_view (u);
361   if (vw == NULL) return;
362   tm_window win= vw->win;
363   if (win == NULL) return;
364   // cout << "Detach view " << vw->buf->buf->name << "\n";
365   vw->win= NULL;
366   widget wid= win->wid;
367   ASSERT (is_attached (wid), "widget should be attached");
368   vw->ed->suspend ();
369   set_scrollable (wid, glue_widget ());
370   win->set_window_name ("TeXmacs");
371   win->set_window_url (url_none ());
372   // cout << "View detached\n";
373 }
374 
375 /******************************************************************************
376 * Switching views
377 ******************************************************************************/
378 
379 void
window_set_view(url win_u,url new_u,bool focus)380 window_set_view (url win_u, url new_u, bool focus) {
381   //cout << "set view " << win_u << ", " << new_u << ", " << focus << "\n";
382   tm_window win= concrete_window (win_u);
383   if (win == NULL) return;
384   //cout << "Found window\n";
385   tm_view new_vw= concrete_view (new_u);
386   if (new_vw == NULL || new_vw->win == win) return;
387   //cout << "Found view\n";
388   ASSERT (new_vw->win == NULL, "view attached to other window");
389   url old_u= window_to_view (win_u);
390   if (!is_none (old_u)) detach_view (old_u);
391   attach_view (win_u, new_u);
392   if (focus || get_current_view () == old_u)
393     set_current_view (new_u);
394 }
395 
396 void
switch_to_buffer(url name)397 switch_to_buffer (url name) {
398   //cout << "Switching to buffer " << name << "\n";
399   url u= get_passive_view (name);
400   tm_view vw= concrete_view (u);
401   if (vw == NULL) return;
402   window_set_view (get_current_window (), u, true);
403   tm_window nwin= vw->win;
404   if (nwin != NULL)
405     nwin->set_window_zoom_factor (nwin->get_window_zoom_factor ());
406   //cout << "Switched to buffer " << new_vw->buf->buf->name << "\n";
407 }
408 
409 void
focus_on_editor(editor ed)410 focus_on_editor (editor ed) {
411   array<url> bufs= get_all_buffers ();
412   for (int i=0; i<N(bufs); i++) {
413     array<url> vs= buffer_to_views (bufs[i]);
414     for (int j=0; j<N(vs); j++)
415       if (view_to_editor (vs[j]) == ed) {
416 	set_current_view (vs[j]);
417 	return;
418       }
419   }
420 
421   /* FIXME: directly using get_all_views produces synchronization error
422   array<url> vs= get_all_views ();
423   for (int i=0; i<N(vs); i++)
424     if (view_to_editor (vs[i]) == ed) {
425       cout << "Focus on " << vs[i] << "\n";
426       set_current_view (vs[i]);
427       return;
428     }
429   */
430 
431   std_warning << "Warning: editor no longer exists, "
432               << "may indicate synchronization error\n";
433   //failed_error << "Name of buffer: " << ed->buf->buf->name << "\n";
434   //FAILED ("invalid situation");
435 }
436 
437 bool
focus_on_buffer(url name)438 focus_on_buffer (url name) {
439   // Focus on the most recent view on a buffer, preferably active in a window
440   // Return false if no view exists for the buffer
441   if (the_view != NULL && the_view->buf->buf->name == name) return true;
442   url r= get_recent_view (name, true, false, true, false);
443   if (is_none (r)) r= get_recent_view (name, true, false, false, false);
444   if (is_none (r)) {
445     array<url> vws= buffer_to_views (name);
446     if (N(vws) > 0) r= vws[0];
447   }
448   if (is_none (r)) return false;
449   set_current_view (r);
450   return true;
451 }
452