]> git.lyx.org Git - features.git/blob - src/frontends/gtk/GWorkArea.C
This is the merging of the GUI API cleanup branch that was developed in svn+ssh:...
[features.git] / src / frontends / gtk / GWorkArea.C
1 /**
2  * \file GWorkArea.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Huang Ying
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 // Too hard to make concept checks work with this file
14 #ifdef _GLIBCXX_CONCEPT_CHECKS
15 #undef _GLIBCXX_CONCEPT_CHECKS
16 #endif
17 #ifdef _GLIBCPP_CONCEPT_CHECKS
18 #undef _GLIBCPP_CONCEPT_CHECKS
19 #endif
20
21 #include "GWorkArea.h"
22 #include "GView.h"
23 #include "GtkmmX.h"
24 #include "GLyXKeySym.h"
25
26 #include "BufferView.h"
27 #include "debug.h"
28 #include "funcrequest.h"
29 #include "LColor.h"
30
31 using boost::shared_ptr;
32
33 using std::string;
34
35 namespace lyx {
36 namespace frontend {
37
38 namespace {
39
40 mouse_button::state gButtonToLyx(guint gdkbutton)
41 {
42         // GDK uses int 1,2,3 but lyx uses enums (1,2,4)
43         switch (gdkbutton) {
44         case 1:
45                 return mouse_button::button1;
46         case 2:
47                 return mouse_button::button2;
48         case 3:
49                 return mouse_button::button3;
50         case 4:
51                 return mouse_button::button4;
52         case 5:
53                 return mouse_button::button5;
54         }
55
56         // This shouldn't happen, according to gdk docs
57         lyxerr << "gButtonToLyx: unhandled button index\n";
58         return mouse_button::button1;
59 }
60
61 } // namespace anon
62
63 ColorCache colorCache;
64
65 Gdk::Color * ColorCache::getColor(LColor_color clr)
66 {
67         MapIt it = cache_.find(clr);
68         return it == cache_.end() ? 0 : it->second.get();
69 }
70
71
72 XftColor * ColorCache::getXftColor(LColor_color clr)
73 {
74         MapIt2 it = cache2_.find(clr);
75         return it == cache2_.end() ? 0 : it->second.get();
76 }
77
78
79 void ColorCache::cacheColor(LColor_color clr, Gdk::Color * gclr)
80 {
81         cache_[clr] = shared_ptr<Gdk::Color>(gclr);
82 }
83
84
85 void ColorCache::cacheXftColor(LColor_color clr, XftColor * xclr)
86 {
87         cache2_[clr] = shared_ptr<XftColor>(xclr);
88 }
89
90
91 void ColorCache::clear()
92 {
93         cache_.clear();
94         cache2_.clear();
95 }
96
97
98 XftColor * ColorHandler::getXftColor(LColor_color clr)
99 {
100         XftColor * xclr = colorCache.getXftColor(clr);
101         if (!xclr) {
102                 xclr = new XftColor;
103                 Colormap colormap = GDK_COLORMAP_XCOLORMAP(
104                         owner_.getColormap()->gobj());
105                 Visual * visual = GDK_VISUAL_XVISUAL(
106                         owner_.getColormap()->get_visual()->gobj());
107                 XftColorAllocName(owner_.getDisplay(), visual, colormap,
108                                   const_cast<char*>(
109                                           lcolor.getX11Name(clr).c_str())
110                                   , xclr);
111                 colorCache.cacheXftColor(clr, xclr);
112         }
113         return xclr;
114 }
115
116
117 Gdk::Color * ColorHandler::getGdkColor(LColor_color clr)
118 {
119         Gdk::Color * gclr = colorCache.getColor(clr);
120         if (!gclr) {
121                 gclr = new Gdk::Color;
122                 gclr->parse(lcolor.getX11Name(clr));
123                 owner_.getColormap()->alloc_color(*gclr);
124                 colorCache.cacheColor(clr, gclr);
125         }
126         return gclr;
127 }
128
129
130 namespace
131 {
132
133
134 mouse_button::state gtkButtonState(unsigned int state)
135 {
136         mouse_button::state b = mouse_button::none;
137         if (state & GDK_BUTTON1_MASK)
138                 b = mouse_button::button1;
139         else if (state & GDK_BUTTON2_MASK)
140                 b = mouse_button::button2;
141         else if (state & GDK_BUTTON3_MASK)
142                 b = mouse_button::button3;
143         else if (state & GDK_BUTTON3_MASK)
144                 b = mouse_button::button3;
145         else if (state & GDK_BUTTON4_MASK)
146                 b = mouse_button::button4;
147         else if (state & GDK_BUTTON5_MASK)
148                 b = mouse_button::button5;
149         return b;
150 }
151
152
153 key_modifier::state gtkKeyState(guint state)
154 {
155         key_modifier::state k = key_modifier::none;
156         if (state & GDK_CONTROL_MASK)
157                 k |= key_modifier::ctrl;
158         if (state & GDK_SHIFT_MASK)
159                 k |= key_modifier::shift;
160         if (state & GDK_MOD1_MASK)
161                 k |= key_modifier::alt;
162         return k;
163 }
164
165
166 void inputCommitRelay(GtkIMContext */*imcontext*/, gchar * str, GWorkArea * area)
167 {
168         area->inputCommit(str);
169 }
170
171
172 }
173
174
175 GWorkArea::GWorkArea(LyXView & owner, int width, int height)
176     : view_(owner), workAreaPixmap_(0), painter_(*this), draw_(0), colorHandler_(*this),
177           adjusting_(false)
178 {
179         workArea_.set_size_request(width, height);
180         workArea_.set_double_buffered(false);
181         workArea_.add_events(Gdk::STRUCTURE_MASK | Gdk::EXPOSURE_MASK |
182                              Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK |
183                              Gdk::KEY_PRESS_MASK | Gdk::BUTTON1_MOTION_MASK);
184         workArea_.signal_expose_event().connect(
185                 sigc::mem_fun(*this, &GWorkArea::onExpose));
186         workArea_.signal_configure_event().connect(
187                 sigc::mem_fun(*this, &GWorkArea::onConfigure));
188         workArea_.signal_button_press_event().connect(
189                 sigc::mem_fun(*this, &GWorkArea::onButtonPress));
190         workArea_.signal_button_release_event().connect(
191                 sigc::mem_fun(*this, &GWorkArea::onButtonRelease));
192         workArea_.signal_key_press_event().connect(
193                 sigc::mem_fun(*this, &GWorkArea::onKeyPress));
194         workArea_.signal_motion_notify_event().connect(
195                 sigc::mem_fun(*this, &GWorkArea::onMotionNotify));
196         workArea_.show();
197         vscrollbar_.get_adjustment()->signal_value_changed().connect(
198                 sigc::mem_fun(*this, &GWorkArea::onScroll));
199         workArea_.signal_scroll_event().connect(
200                 sigc::mem_fun(*this, &GWorkArea::onScrollWheel));
201         vscrollbar_.show();
202         hbox_.children().push_back(Gtk::Box_Helpers::Element(workArea_));
203         hbox_.children().push_back(
204                 Gtk::Box_Helpers::Element(vscrollbar_,Gtk::PACK_SHRINK));
205         hbox_.show();
206
207         GView & gview = static_cast<GView &>(owner);
208         gview.getBox(GView::Center).children().push_back(
209                 Gtk::Box_Helpers::Element(hbox_));
210
211         workArea_.set_flags(workArea_.get_flags() | Gtk::CAN_DEFAULT |
212                             Gtk::CAN_FOCUS);
213         workArea_.grab_default();
214         gview.setGWorkArea(&workArea_);
215         imContext_ = GTK_IM_CONTEXT(gtk_im_multicontext_new());
216         g_signal_connect(G_OBJECT(imContext_), "commit",
217                          G_CALLBACK(&inputCommitRelay),
218                          this);
219 }
220
221
222 GWorkArea::~GWorkArea()
223 {
224         g_object_unref(imContext_);
225 }
226
227
228 Painter & GWorkArea::getPainter()
229 {
230         return painter_;
231 }
232
233
234 int GWorkArea::workWidth() const
235 {
236         return workArea_.get_width();
237 }
238
239
240 int GWorkArea::workHeight() const
241 {
242         return workArea_.get_height();
243 }
244
245
246 int GWorkArea::xpos() const
247 {
248         return 0;
249 }
250
251
252 int GWorkArea::ypos() const
253 {
254         return 0;
255 }
256
257
258 Glib::RefPtr<Gdk::Window> GWorkArea::getWindow()
259 {
260         return workArea_.get_window();
261 }
262
263
264 Display * GWorkArea::getDisplay() const
265 {
266         return GDK_WINDOW_XDISPLAY(
267                 const_cast<GdkWindow*>(workArea_.get_window()->gobj()));
268 }
269
270
271 Glib::RefPtr<Gdk::Pixmap> GWorkArea::getPixmap()
272 {
273         return workAreaPixmap_;
274 }
275
276
277 Glib::RefPtr<Gdk::GC> GWorkArea::getGC()
278 {
279         return workAreaGC_;
280 }
281
282
283 Glib::RefPtr<Gdk::Colormap> GWorkArea::getColormap()
284 {
285         return workArea_.get_colormap();
286 }
287
288
289 XftDraw * GWorkArea::getXftDraw()
290 {
291         return draw_;
292 }
293
294
295 ColorHandler & GWorkArea::getColorHandler()
296 {
297         return colorHandler_;
298 }
299
300
301 bool GWorkArea::onExpose(GdkEventExpose * event)
302 {
303         workArea_.get_window()->draw_drawable(
304                 workArea_.get_style()->get_black_gc(),
305                 workAreaPixmap_,
306                 event->area.x, event->area.y,
307                 event->area.x, event->area.y,
308                 event->area.width, event->area.height);
309         return true;
310 }
311
312
313 bool GWorkArea::onConfigure(GdkEventConfigure * /*event*/)
314 {
315         int x, y, width, height, depth;
316         workArea_.get_window()->get_geometry(x, y, width, height, depth);
317         if (draw_)
318                 XftDrawDestroy(draw_);
319         workAreaPixmap_ = Gdk::Pixmap::create(workArea_.get_window(),
320                                               width, height, depth);
321         Pixmap pixmap = GDK_PIXMAP_XID(workAreaPixmap_->gobj());
322         Colormap colormap = GDK_COLORMAP_XCOLORMAP(
323                 workArea_.get_colormap()->gobj());
324         Visual * visual = GDK_VISUAL_XVISUAL(
325                 workArea_.get_colormap()->get_visual()->gobj());
326         draw_ = XftDrawCreate(getDisplay(), pixmap,
327                               visual, colormap);
328         if (!workAreaGC_) {
329                 workAreaGC_ = Gdk::GC::create(workArea_.get_window());
330                 Gdk::Cursor cursor(Gdk::XTERM);
331                 workArea_.get_window()->set_cursor(cursor);
332                 gtk_im_context_set_client_window(
333                         imContext_, workArea_.get_window()->gobj());
334         }
335         view_.view()->workAreaResize();
336         return true;
337 }
338
339
340 void GWorkArea::setScrollbarParams(int height, int pos, int line_height)
341 {
342         if (adjusting_)
343                 return;
344
345         adjusting_ = true;
346
347         Gtk::Adjustment * adjustment = vscrollbar_.get_adjustment();
348         adjustment->set_lower(0);
349         int workAreaHeight = workHeight();
350         if (!height || height < workAreaHeight) {
351                 adjustment->set_upper(workAreaHeight);
352                 adjustment->set_page_size(workAreaHeight);
353                 adjustment->set_value(0);
354                 adjustment->changed();
355                 adjusting_ = false;
356                 return;
357         }
358         adjustment->set_step_increment(line_height * 3);
359         adjustment->set_page_increment(workAreaHeight - line_height);
360         // Allow the user half a screen of blank at the end
361         // to make scrollbar consistant with centering the cursor
362         adjustment->set_upper(height + workAreaHeight / 4);
363         adjustment->set_page_size(workAreaHeight);
364         adjustment->set_value(pos);
365         adjustment->changed();
366         adjusting_ = false;
367 }
368
369
370 void GWorkArea::onScroll()
371 {
372         if (adjusting_)
373                 return;
374
375         adjusting_ = true;
376
377         double val = vscrollbar_.get_adjustment()->get_value();
378         view_.view()->scrollDocView(static_cast<int>(val));
379         adjusting_ = false;
380 }
381
382
383 bool GWorkArea::onScrollWheel(GdkEventScroll * event)
384 {
385         Gtk::Adjustment * adjustment = vscrollbar_.get_adjustment();
386
387         double step;
388         if (event->state & GDK_CONTROL_MASK)
389                 step = adjustment->get_page_increment();
390         else
391                 step = adjustment->get_step_increment();
392
393         if (event->direction == GDK_SCROLL_UP)
394                 step *= -1.0f;
395
396         double target = adjustment->get_value() + step;
397         // Prevent the user getting a whole screen of blank when they
398         // try to scroll past the end of the doc
399         double max = adjustment->get_upper() - workHeight();
400         if (target > max)
401                 target = max;
402
403         adjustment->set_value(target);
404         return true;
405 }
406
407
408 bool GWorkArea::onButtonPress(GdkEventButton * event)
409 {
410         kb_action ka = LFUN_MOUSE_PRESS;
411         switch (event->type) {
412         case GDK_BUTTON_PRESS:
413                 ka = LFUN_MOUSE_PRESS;
414                 break;
415         case GDK_2BUTTON_PRESS:
416                 ka = LFUN_MOUSE_DOUBLE;
417                 break;
418         case GDK_3BUTTON_PRESS:
419                 ka = LFUN_MOUSE_TRIPLE;
420                 break;
421         default:
422                 break;
423         }
424         view_.view()->workAreaDispatch(FuncRequest(ka,
425                              static_cast<int>(event->x),
426                              static_cast<int>(event->y),
427                              gButtonToLyx(event->button)));
428         workArea_.grab_focus();
429         return true;
430 }
431
432
433 bool GWorkArea::onButtonRelease(GdkEventButton * event)
434 {
435         view_.view()->workAreaDispatch(FuncRequest(LFUN_MOUSE_RELEASE,
436                              static_cast<int>(event->x),
437                              static_cast<int>(event->y),
438                              gButtonToLyx(event->button)));
439         return true;
440 }
441
442
443 bool GWorkArea::onMotionNotify(GdkEventMotion * event)
444 {
445         static guint32 timeBefore;
446         Gtk::Adjustment * adjustment = vscrollbar_.get_adjustment();
447         double step = adjustment->get_step_increment();
448         double value = adjustment->get_value();
449         if (event->x < 0)
450                 value -= step;
451         else if (event->x > workArea_.get_height())
452                 value += step;
453         if (value != adjustment->get_value()) {
454                 if (event->time - timeBefore > 200) {
455                         adjustment->set_value(value);
456                         adjustment->value_changed();
457                 }
458                 timeBefore = event->time;
459         }
460         view_.view()->workAreaDispatch(FuncRequest(LFUN_MOUSE_MOTION,
461                              static_cast<int>(event->x),
462                              static_cast<int>(event->y),
463                              gtkButtonState(event->state)));
464         return true;
465 }
466
467
468 void GWorkArea::inputCommit(gchar * str)
469 {
470         inputCache_ = Glib::locale_from_utf8(str);
471 }
472
473
474 bool GWorkArea::onKeyPress(GdkEventKey * event)
475 {
476 #ifdef I18N
477         inputCache_ = "";
478         bool inputGet = gtk_im_context_filter_keypress(imContext_, event);
479         // cope with ascii
480         if ((inputGet && inputCache_.size() == 1 && inputCache_[0] < 128) ||
481             !inputGet) {
482 #endif
483                 GLyXKeySym *glk = new GLyXKeySym(event->keyval);
484                 view_.view()->workAreaKeyPress(LyXKeySymPtr(glk),
485                                  gtkKeyState(event->state));
486 #ifdef I18N
487         } else if (!inputCache_.empty())
488                 workAreaCJK_IMprocess(inputCache_.size(), inputCache_.data());
489 #endif
490         return true;
491 }
492
493
494 void GWorkArea::onClipboardGet(Gtk::SelectionData & /*selection_data*/,
495                                guint /*info*/)
496 {
497         view_.view()->selectionRequested();
498 }
499
500
501 void GWorkArea::onClipboardClear()
502 {
503 //      selectionLost();
504 }
505
506
507 void GWorkArea::haveSelection(bool toHave)
508 {
509         if (toHave) {
510                 Glib::RefPtr<Gtk::Clipboard> clipboard =
511                         Gtk::Clipboard::get(GDK_SELECTION_PRIMARY);
512                 std::vector<Gtk::TargetEntry> listTargets;
513                 listTargets.push_back(Gtk::TargetEntry("UTF8_STRING"));
514                 clipboard->set(listTargets,
515                                sigc::mem_fun(const_cast<GWorkArea&>(*this),
516                                           &GWorkArea::onClipboardGet),
517                                sigc::mem_fun(const_cast<GWorkArea&>(*this),
518                                           &GWorkArea::onClipboardClear));
519         }
520 }
521
522
523 // ENCODING: Gtk::Clipboard returns UTF-8, we assume that the backend
524 // wants ISO-8859-1 and convert it to that.
525 string const GWorkArea::getClipboard() const
526 {
527         Glib::RefPtr<Gtk::Clipboard> clipboard =
528                 Gtk::Clipboard::get(GDK_SELECTION_PRIMARY);
529         return Glib::convert_with_fallback(
530                 clipboard->wait_for_text(), "ISO-8859-1", "UTF-8");
531 }
532
533
534 // ENCODING: we assume that the backend passes us ISO-8859-1 and
535 // convert from that to UTF-8 before passing to GTK
536 void GWorkArea::putClipboard(string const & str)
537 {
538         Glib::RefPtr<Gtk::Clipboard> clipboard =
539                 Gtk::Clipboard::get(GDK_SELECTION_PRIMARY);
540         clipboard->set_text(Glib::convert(str, "UTF-8", "ISO-8859-1"));
541 }
542
543
544 } // namespace frontend
545 } // namespace lyx