comparison engine/core/gui/guichan/guichanguimanager.cpp @ 697:ecaa4d98f05f tip

Abstracted the GUI code and refactored the GUIChan-specific code into its own module. * Most of the GUIChan code has been refactored into its own gui/guichan module. However, references to the GuiFont class still persist in the Engine and GuiManager code and these will need further refactoring. * GuiManager is now an abstract base class which specific implementations (e.g. GUIChan) should subclass. * The GUIChan GUI code is now a concrete implementation of GuiManager, most of which is in the new GuiChanGuiManager class. * The GUI code in the Console class has been refactored out of the Console and into the GUIChan module as its own GuiChanConsoleWidget class. The rest of the Console class related to executing commands was left largely unchanged. * Existing client code may need to downcast the GuiManager pointer received from FIFE::Engine::getGuiManager() to GuiChanGuiManager, since not all functionality is represented in the GuiManager abstract base class. Python client code can use the new GuiChanGuiManager.castTo static method for this purpose.
author M. George Hansen <technopolitica@gmail.com>
date Sat, 18 Jun 2011 00:28:40 -1000
parents
children
comparison
equal deleted inserted replaced
696:e201abd8c807 697:ecaa4d98f05f
1 /***************************************************************************
2 * Copyright (C) 2005-2008 by the FIFE team *
3 * http://www.fifengine.de *
4 * This file is part of FIFE. *
5 * *
6 * FIFE is free software; you can redistribute it and/or *
7 * modify it under the terms of the GNU Lesser General Public *
8 * License as published by the Free Software Foundation; either *
9 * version 2.1 of the License, or (at your option) any later version. *
10 * *
11 * This library is distributed in the hope that it will be useful, *
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
14 * Lesser General Public License for more details. *
15 * *
16 * You should have received a copy of the GNU Lesser General Public *
17 * License along with this library; if not, write to the *
18 * Free Software Foundation, Inc., *
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
20 ***************************************************************************/
21
22 // Standard C++ library includes
23 #include <iostream>
24 #include <cassert>
25
26 // 3rd party library includes
27 #include <boost/filesystem/convenience.hpp>
28 #include <boost/bind.hpp>
29 #include <boost/lexical_cast.hpp>
30 #include <boost/regex.hpp>
31 #include <boost/tokenizer.hpp>
32 #include <guichan/sdl/sdlinput.hpp>
33 #include <guichan/key.hpp>
34 #include <guichan/focushandler.hpp>
35 #include <guichan.hpp>
36
37 // FIFE includes
38 // These includes are split up in two parts, separated by one empty line
39 // First block: files included from the FIFE root src directory
40 // Second block: files included from the same folder
41 #include "util/base/exception.h"
42 #include "util/log/logger.h"
43 #include "video/renderbackend.h"
44 #include "gui/console.h"
45 #include "video/fonts/fontbase.h"
46 #include "video/fonts/truetypefont.h"
47 #include "video/fonts/subimagefont.h"
48 #include "video/renderbackend.h"
49 #include "eventchannel/key/ec_key.h"
50 #include "eventchannel/key/ec_keyevent.h"
51 #include "eventchannel/mouse/ec_mouseevent.h"
52 #include "util/time/timemanager.h"
53 #include "util/log/logger.h"
54 #include "util/base/exception.h"
55
56 #include "commandline.h"
57 #include "gui_font.h"
58 #include "gui_imageloader.h"
59 #include "guichanguimanager.h"
60 #ifdef HAVE_OPENGL
61 #include "opengl/opengl_gui_graphics.h"
62 #endif
63 #include "sdl/sdl_gui_graphics.h"
64 #include "widgets/utf8textbox.h"
65
66 namespace FIFE {
67 static Logger _log(LM_GUI);
68
69 const unsigned GuiChanConsoleWidget::m_maxOutputRows = 50;
70
71 GuiChanConsoleWidget::GuiChanConsoleWidget() :
72 gcn::Container(),
73 m_input(new CommandLine()),
74 m_output(new gcn::UTF8TextBox()),
75 m_outputscrollarea(new gcn::ScrollArea(m_output)),
76 m_status(new gcn::Label()),
77 m_toolsbutton(new gcn::Button("Tools")) {
78
79 reLayout();
80 add(m_outputscrollarea);
81 add(m_input);
82 add(m_status);
83 add(m_toolsbutton);
84
85 setOpaque(true);
86
87 m_input->setCallback(std::bind1st(std::mem_fun(&Console::execute),
88 m_console));
89 m_prompt = "-- ";
90
91 m_isAttached = false;
92
93 m_fpsTimer.setInterval(500);
94 m_fpsTimer.setCallback(
95 boost::bind(&GuiChanConsoleWidget::updateCaption, this));
96
97 m_hiding = true;
98
99 m_animationTimer.setInterval(20);
100 m_animationTimer.setCallback(
101 boost::bind(&GuiChanConsoleWidget::updateAnimation, this));
102
103 m_toolsbutton->addActionListener(this);
104 m_toolsbutton->setFocusable(false);
105 m_input->addFocusListener(this);
106
107 GuiFont* font = GuiChanGuiManager::instance()->createFont();
108 font->setColor(255, 255, 255);
109 setIOFont(font);
110 }
111
112 void GuiChanConsoleWidget::reLayout() {
113 Image* screen = RenderBackend::instance()->getScreenImage();
114 assert(screen);
115
116 int w, h, b, input_h, bbar_h, button_w;
117 w = screen->getWidth() * 4 / 5;
118 h = screen->getHeight() * 4 / 5;
119 b = 0;
120 input_h = getFont()->getHeight();
121 bbar_h = input_h;
122 button_w = 80;
123
124 gcn::Color black(0x00, 0, 0, 0xff);
125 gcn::Color white(0xff, 0xff, 0xff, 0xff);
126 gcn::Color dark(50, 60, 50, 0xff);
127
128 setSize(w, h);
129 setPosition((screen->getWidth() - w) / 2, -h);
130 setFrameSize(0);
131
132 setForegroundColor(white);
133 setBackgroundColor(black);
134 setBaseColor(dark);
135
136 setSize(w, h);
137
138 m_outputscrollarea->setSize(w - 2 * b, h - input_h - 3 * b - bbar_h);
139 m_outputscrollarea->setPosition(b, 0);
140
141 m_input->setPosition(b, h - input_h - b - bbar_h);
142 m_input->setSize(w - 2 * b, input_h);
143
144 m_status->setPosition(b, h - b - bbar_h);
145 m_status->setSize(w - 2 * b, bbar_h);
146
147 m_toolsbutton->setPosition(w - button_w, h - b - bbar_h);
148 m_toolsbutton->setSize(button_w, bbar_h);
149
150 m_output->setBackgroundColor(black);
151 m_output->setFocusable(false);
152
153 m_outputscrollarea->setBackgroundColor(black);
154 m_outputscrollarea->setBaseColor(dark);
155
156 m_input->setForegroundColor(white);
157 m_input->setBackgroundColor(black);
158
159 m_status->setForegroundColor(white);
160 m_status->setBackgroundColor(black);
161
162 m_toolsbutton->setForegroundColor(white);
163 m_toolsbutton->setBackgroundColor(black);
164 m_toolsbutton->setBaseColor(dark);
165
166 m_hiddenPos = -h;
167 m_animationDelta = h / 6;
168 }
169
170 GuiChanConsoleWidget::~GuiChanConsoleWidget() {
171 doHide();
172
173 remove(m_input);
174 remove(m_outputscrollarea);
175 remove(m_status);
176
177 delete m_output;
178 delete m_input;
179 delete m_outputscrollarea;
180 delete m_status;
181 delete m_toolsbutton;
182 }
183
184 void GuiChanConsoleWidget::updateCaption() {
185 std::string caption = "FIFE Console - FPS: ";
186 float fps = 1e3 / double(TimeManager::instance()->getAverageFrameTime());
187 caption += boost::lexical_cast<std::string>(fps);
188 m_status->setCaption(caption);
189 }
190
191 void GuiChanConsoleWidget::updateAnimation() {
192 if (m_hiding) {
193 setPosition(getX(), getY() - m_animationDelta);
194 if (getY() <= m_hiddenPos) {
195 doHide();
196 m_animationTimer.stop();
197 }
198 } else {
199 setPosition(getX(), getY() + m_animationDelta);
200 if (getY() >= 0) {
201 setPosition(getX(), 0);
202 m_animationTimer.stop();
203 }
204 }
205 }
206
207 void GuiChanConsoleWidget::clear() {
208 m_output->setText("");
209 }
210
211 void GuiChanConsoleWidget::doShow() {
212 if (m_isAttached)
213 return;
214 m_isAttached = true;
215 GuiChanGuiManager::instance()->add(this);
216 GuiChanGuiManager::instance()->getTopContainer()->moveToTop(this);
217 // Assure the input field is focused when shown.
218 m_input->requestFocus();
219
220 m_fpsTimer.start();
221 }
222
223 void GuiChanConsoleWidget::doHide() {
224 if (!m_isAttached)
225 return;
226 m_isAttached = false;
227 GuiChanGuiManager::instance()->remove(this);
228 m_fpsTimer.stop();
229 }
230
231 void GuiChanConsoleWidget::show() {
232 if (m_hiding) {
233 m_hiding = false;
234 doShow();
235 m_animationTimer.start();
236 }
237 }
238
239 void GuiChanConsoleWidget::hide() {
240 if (!m_hiding) {
241 m_hiding = true;
242 m_animationTimer.start();
243 }
244 }
245
246 void GuiChanConsoleWidget::toggleShowHide() {
247 m_hiding = !m_hiding;
248 if (!m_hiding)
249 doShow();
250 m_animationTimer.start();
251 }
252
253 void GuiChanConsoleWidget::println(const std::string& s) {
254 assert(m_output);
255
256 // Add the text in rows
257 boost::char_separator<char> separator("\n");
258 typedef boost::tokenizer< boost::char_separator<char> > tokenizer;
259 tokenizer tokens(s, separator);
260 for (tokenizer::iterator i = tokens.begin(); i != tokens.end(); ++i) {
261 m_output->addRow(*i);
262 }
263
264 // Assure the maximum number of rows
265 if (m_output->getNumberOfRows() > m_maxOutputRows) {
266 unsigned rows = m_output->getNumberOfRows();
267 int delta_rows = rows - m_maxOutputRows;
268 std::vector<std::string> rows_text;
269 for (size_t i = delta_rows; i != rows; ++i) {
270 rows_text.push_back(m_output->getTextRow(i));
271 }
272 m_output->setText("");
273 for (size_t i = 0; i != rows_text.size(); ++i) {
274 m_output->addRow(rows_text[i]);
275 }
276 }
277
278 // Assure the new text is visible
279 gcn::Rectangle rect(0, m_output->getHeight(), 0, 0);
280 m_outputscrollarea->showWidgetPart(m_output, rect);
281 }
282
283 void GuiChanConsoleWidget::action(const gcn::ActionEvent& event) {
284 if (m_console) {
285 m_console->getConsoleExecuter()->onToolsClick();
286 } else {
287 FL_WARN(_log, "Console not bound, but tools button clicked");
288 }
289 }
290
291 void GuiChanConsoleWidget::setIOFont(GuiFont* font) {
292 m_input->setFont(font);
293 m_output->setFont(font);
294 }
295
296 void GuiChanConsoleWidget::focusLost(const gcn::Event&) {
297 hide();
298 }
299
300
301 GuiChanGuiManager::GuiChanGuiManager(ImagePool& pool) :
302 GuiManager(pool),
303 m_gui_graphics(0),
304 m_gcn_gui(new gcn::Gui()),
305 m_focushandler(0),
306 m_gcn_topcontainer(new gcn::Container()),
307 m_image_loader(new GuiImageLoader(pool)),
308 m_input(new gcn::SDLInput()),
309 m_widgets(),
310 m_console_widget(0),
311 m_had_mouse(false),
312 m_fontpath(),
313 m_fontglyphs(),
314 m_fontsize(),
315 m_logic_executed(false) {
316
317 m_gcn_gui->setInput(m_input);
318 gcn::Image::setImageLoader(m_image_loader);
319
320 m_gcn_gui->setTop(m_gcn_topcontainer);
321 m_focushandler = m_gcn_topcontainer->_getFocusHandler();
322
323 m_gcn_topcontainer->setOpaque(false);
324 m_gcn_topcontainer->setFocusable(false);
325 }
326
327 GuiChanGuiManager::~GuiChanGuiManager() {
328 delete m_console_widget;
329 delete m_gcn_topcontainer;
330 delete m_image_loader;
331 delete m_input;
332 delete m_gcn_gui;
333 delete m_gui_graphics;
334 }
335
336 bool GuiChanGuiManager::onSdlEvent(SDL_Event& evt) {
337 if (!m_input) {
338 FL_WARN(_log, "GuiChanGuiManager, GuichanGUI->getInput == 0 ... discarding events!");
339 return false;
340 }
341
342 switch(evt.type) {
343 case SDL_MOUSEBUTTONDOWN:
344 case SDL_MOUSEBUTTONUP:
345 if( m_gcn_topcontainer->getWidgetAt(evt.button.x,evt.button.y) ) {
346 m_input->pushInput(evt);
347 return true;
348 }
349 m_focushandler->focusNone();
350 return false;
351
352 case SDL_MOUSEMOTION:
353 if( m_gcn_topcontainer->getWidgetAt(evt.button.x,evt.button.y) ) {
354 m_had_mouse = true;
355 m_input->pushInput(evt);
356 return true;
357 }
358 if( m_had_mouse ) {
359 // We only keep the mouse if a widget/window has requested
360 // dragging.
361 m_had_mouse = bool(m_focushandler->getDraggedWidget());
362 m_input->pushInput(evt);
363 return true;
364 }
365 return false;
366
367 case SDL_KEYDOWN:
368 case SDL_KEYUP:
369 if(m_focushandler->getFocused()) {
370 m_input->pushInput(evt);
371 return true;
372 }
373 return false;
374
375 case SDL_ACTIVEEVENT:
376 // Actually Guichan doesn't care (it should!)
377 // so at least don't swallow mouse_focus events up.
378 return false;
379
380 default:
381 return false;
382 }
383 }
384
385 void GuiChanGuiManager::resize(unsigned int x, unsigned int y,
386 unsigned int width, unsigned int height) {
387 m_gcn_topcontainer->setDimension(gcn::Rectangle(x, y, width, height));
388 if (m_console_widget) {
389 m_console_widget->reLayout();
390 }
391 }
392
393 gcn::Gui* GuiChanGuiManager::getGuiChanGui() const {
394 return m_gcn_gui;
395 }
396
397 void GuiChanGuiManager::add(gcn::Widget* widget) {
398 if( !m_widgets.count(widget) ) {
399 m_gcn_topcontainer->add(widget);
400 m_widgets.insert(widget);
401 }
402 }
403
404 void GuiChanGuiManager::remove(gcn::Widget* widget) {
405 if( m_widgets.count(widget) ) {
406 m_widgets.erase(widget);
407 m_gcn_topcontainer->remove(widget);
408 }
409 }
410
411 void GuiChanGuiManager::init(RenderBackend* render_backend, int screenWidth,
412 int screenHeight) {
413 GuiManager::init(render_backend, screenWidth, screenHeight);
414
415 #ifdef HAVE_OPENGL
416 if(render_backend->getName() != "SDL") {
417 m_gui_graphics = new OpenGLGuiGraphics(getImagePool());
418 }
419 #endif
420 if(render_backend->getName() == "SDL") {
421 m_gui_graphics = new SdlGuiGraphics(getImagePool());
422 }
423
424 m_gcn_gui->setGraphics(m_gui_graphics);
425
426 m_console_widget = new GuiChanConsoleWidget();
427 resize(0, 0, screenWidth, screenHeight);
428 }
429
430 void GuiChanGuiManager::turn() {
431 if (!m_logic_executed)
432 m_gcn_gui->logic();
433 m_logic_executed = false;
434 m_gcn_gui->draw();
435 }
436
437 void GuiChanGuiManager::showConsole() {
438 if (!m_console_visible) {
439 m_console_widget->show();
440 m_console_visible = true;
441 }
442 }
443
444 void GuiChanGuiManager::hideConsole() {
445 if(m_console_visible) {
446 m_console_widget->hide();
447 m_console_visible = false;
448 }
449 }
450
451 GuiFont* GuiChanGuiManager::setDefaultFont(const std::string& path,
452 unsigned int size, const std::string& glyphs) {
453 printf("made it!");
454 GuiFont* default_font = GuiManager::setDefaultFont(path, size, glyphs);
455 gcn::Widget::setGlobalFont(default_font);
456 if (m_console_widget) {
457 m_console_widget->reLayout();
458 }
459 return default_font;
460 }
461
462 KeyEvent GuiChanGuiManager::translateKeyEvent(const gcn::KeyEvent& gcnevt) {
463 KeyEvent keyevt;
464 if(gcnevt.getType() == gcn::KeyEvent::PRESSED)
465 keyevt.setType(KeyEvent::PRESSED);
466 else if(gcnevt.getType() == gcn::KeyEvent::RELEASED)
467 keyevt.setType(KeyEvent::RELEASED);
468 else
469 throw EventException("Invalid event type in fillKeyEvent");
470 keyevt.setShiftPressed(gcnevt.isShiftPressed());
471 keyevt.setControlPressed(gcnevt.isControlPressed());
472 keyevt.setAltPressed(gcnevt.isAltPressed());
473 keyevt.setMetaPressed(gcnevt.isMetaPressed());
474 keyevt.setNumericPad(gcnevt.isNumericPad());
475
476 // Convert from guichan keyval to FIFE keyval
477 int keyval = gcnevt.getKey().getValue();
478 keyval = convertGuichanKeyToFifeKey(keyval);
479
480 keyevt.setKey(Key(static_cast<Key::KeyType>(keyval), keyval));
481
482 return keyevt;
483 }
484
485 MouseEvent GuiChanGuiManager::translateMouseEvent(const gcn::MouseEvent& gcnevt) {
486 MouseEvent mouseevt;
487 mouseevt.setShiftPressed(gcnevt.isShiftPressed());
488 mouseevt.setControlPressed(gcnevt.isControlPressed());
489 mouseevt.setAltPressed(gcnevt.isAltPressed());
490 mouseevt.setMetaPressed(gcnevt.isMetaPressed());
491 mouseevt.setX(gcnevt.getX());
492 mouseevt.setY(gcnevt.getY());
493
494 switch(gcnevt.getType()) {
495 case gcn::MouseEvent::PRESSED:
496 mouseevt.setType(MouseEvent::PRESSED);
497 break;
498 case gcn::MouseEvent::RELEASED:
499 mouseevt.setType(MouseEvent::RELEASED);
500 break;
501 case gcn::MouseEvent::MOVED:
502 mouseevt.setType(MouseEvent::MOVED);
503 break;
504 case gcn::MouseEvent::CLICKED:
505 mouseevt.setType(MouseEvent::CLICKED);
506 break;
507 case gcn::MouseEvent::ENTERED:
508 mouseevt.setType(MouseEvent::ENTERED);
509 break;
510 case gcn::MouseEvent::EXITED:
511 mouseevt.setType(MouseEvent::EXITED);
512 break;
513 case gcn::MouseEvent::DRAGGED:
514 mouseevt.setType(MouseEvent::DRAGGED);
515 break;
516 case gcn::MouseEvent::WHEEL_MOVED_DOWN:
517 mouseevt.setType(MouseEvent::WHEEL_MOVED_DOWN);
518 break;
519 case gcn::MouseEvent::WHEEL_MOVED_UP:
520 mouseevt.setType(MouseEvent::WHEEL_MOVED_UP);
521 break;
522 default:
523 mouseevt.setType(MouseEvent::UNKNOWN_EVENT);
524 }
525
526 switch(gcnevt.getButton()) {
527 case gcn::MouseInput::LEFT:
528 mouseevt.setButton(MouseEvent::LEFT);
529 break;
530 case gcn::MouseInput::RIGHT:
531 mouseevt.setButton(MouseEvent::RIGHT);
532 break;
533 case gcn::MouseInput::MIDDLE:
534 mouseevt.setButton(MouseEvent::MIDDLE);
535 break;
536 default:
537 mouseevt.setButton(MouseEvent::UNKNOWN_BUTTON);
538 break;
539 }
540 return mouseevt;
541 }
542
543 int GuiChanGuiManager::convertGuichanKeyToFifeKey(int value) {
544 switch (value) {
545 case gcn::Key::TAB:
546 value = Key::TAB;
547 break;
548 case gcn::Key::LEFT_ALT:
549 value = Key::LEFT_ALT;
550 break;
551 case gcn::Key::RIGHT_ALT:
552 value = Key::RIGHT_ALT;
553 break;
554 case gcn::Key::LEFT_SHIFT:
555 value = Key::LEFT_SHIFT;
556 break;
557 case gcn::Key::RIGHT_SHIFT:
558 value = Key::RIGHT_SHIFT;
559 break;
560 case gcn::Key::LEFT_CONTROL:
561 value = Key::LEFT_CONTROL;
562 break;
563 case gcn::Key::RIGHT_CONTROL:
564 value = Key::RIGHT_CONTROL;
565 break;
566 case gcn::Key::BACKSPACE:
567 value = Key::BACKSPACE;
568 break;
569 case gcn::Key::PAUSE:
570 value = Key::PAUSE;
571 break;
572 case gcn::Key::SPACE:
573 value = Key::SPACE;
574 break;
575 case gcn::Key::ESCAPE:
576 value = Key::ESCAPE;
577 break;
578 case gcn::Key::DELETE:
579 value = Key::DELETE;
580 break;
581 case gcn::Key::INSERT:
582 value = Key::INSERT;
583 break;
584 case gcn::Key::HOME:
585 value = Key::HOME;
586 break;
587 case gcn::Key::END:
588 value = Key::END;
589 break;
590 case gcn::Key::PAGE_UP:
591 value = Key::PAGE_UP;
592 break;
593 case gcn::Key::PRINT_SCREEN:
594 value = Key::PRINT_SCREEN;
595 break;
596 case gcn::Key::PAGE_DOWN:
597 value = Key::PAGE_DOWN;
598 break;
599 case gcn::Key::F1:
600 value = Key::F1;
601 break;
602 case gcn::Key::F2:
603 value = Key::F2;
604 break;
605 case gcn::Key::F3:
606 value = Key::F3;
607 break;
608 case gcn::Key::F4:
609 value = Key::F4;
610 break;
611 case gcn::Key::F5:
612 value = Key::F5;
613 break;
614 case gcn::Key::F6:
615 value = Key::F6;
616 break;
617 case gcn::Key::F7:
618 value = Key::F7;
619 break;
620 case gcn::Key::F8:
621 value = Key::F8;
622 break;
623 case gcn::Key::F9:
624 value = Key::F9;
625 break;
626 case gcn::Key::F10:
627 value = Key::F10;
628 break;
629 case gcn::Key::F11:
630 value = Key::F11;
631 break;
632 case gcn::Key::F12:
633 value = Key::F12;
634 break;
635 case gcn::Key::F13:
636 value = Key::F13;
637 break;
638 case gcn::Key::F14:
639 value = Key::F14;
640 break;
641 case gcn::Key::F15:
642 value = Key::F15;
643 break;
644 case gcn::Key::NUM_LOCK:
645 value = Key::NUM_LOCK;
646 break;
647 case gcn::Key::CAPS_LOCK:
648 value = Key::CAPS_LOCK;
649 break;
650 case gcn::Key::SCROLL_LOCK:
651 value = Key::SCROLL_LOCK;
652 break;
653 case gcn::Key::RIGHT_META:
654 value = Key::RIGHT_META;
655 break;
656 case gcn::Key::LEFT_META:
657 value = Key::LEFT_META;
658 break;
659 case gcn::Key::LEFT_SUPER:
660 value = Key::LEFT_SUPER;
661 break;
662 case gcn::Key::RIGHT_SUPER:
663 value = Key::RIGHT_SUPER;
664 break;
665 case gcn::Key::ALT_GR:
666 value = Key::ALT_GR;
667 break;
668 case gcn::Key::UP:
669 value = Key::UP;
670 break;
671 case gcn::Key::DOWN:
672 value = Key::DOWN;
673 break;
674 case gcn::Key::LEFT:
675 value = Key::LEFT;
676 break;
677 case gcn::Key::RIGHT:
678 value = Key::RIGHT;
679 break;
680 case gcn::Key::ENTER:
681 value = Key::ENTER;
682 break;
683
684 default:
685 // Convert from unicode to lowercase letters
686 if (value >= 1 && value <= 26) {
687 // Control characters
688 value = value - 1 + 'a';
689 } else if (value >= 'A' && value <= 'Z') {
690 value = value - 'A' + 'a';
691 }
692
693 // FIXME: Accented characters (รก) will not get converted properly.
694 break;
695 }
696
697 return value;
698 }
699 }