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