/[svn]/qsampler/trunk/src/qsamplerChannel.cpp
ViewVC logotype

Contents of /qsampler/trunk/src/qsamplerChannel.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 751 - (show annotations) (download)
Fri Aug 19 17:10:16 2005 UTC (18 years, 8 months ago) by capela
File size: 18584 byte(s)
* Added MUTE/SOLO buttons to individual channel strips.

1 // qsamplerChannel.cpp
2 //
3 /****************************************************************************
4 Copyright (C) 2003-2005, rncbc aka Rui Nuno Capela. All rights reserved.
5
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation; either version 2
9 of the License, or (at your option) any later version.
10
11 This program 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
14 GNU 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, USA.
19
20 *****************************************************************************/
21
22 #include "qsamplerChannel.h"
23
24 #include "qsamplerMainForm.h"
25 #include "qsamplerChannelForm.h"
26
27 #include "config.h"
28
29 #include <qfileinfo.h>
30
31 #ifdef CONFIG_LIBGIG
32 #include "gig.h"
33 #endif
34
35 #define QSAMPLER_INSTRUMENT_MAX 8
36
37
38 //-------------------------------------------------------------------------
39 // qsamplerChannel - Sampler channel structure.
40 //
41
42 // Constructor.
43 qsamplerChannel::qsamplerChannel ( qsamplerMainForm *pMainForm, int iChannelID )
44 {
45 m_pMainForm = pMainForm;
46 m_iChannelID = iChannelID;
47
48 // m_sEngineName = noEngineName();
49 // m_sInstrumentName = noInstrumentName();
50 // m_sInstrumentFile = m_sInstrumentName;
51 m_iInstrumentNr = -1;
52 m_iInstrumentStatus = -1;
53 m_sMidiDriver = "ALSA";
54 m_iMidiDevice = -1;
55 m_iMidiPort = -1;
56 m_iMidiChannel = -1;
57 m_sAudioDriver = "ALSA";
58 m_iAudioDevice = -1;
59 m_fVolume = 0.0;
60 m_bMute = false;
61 m_bSolo = false;
62 }
63
64 // Default destructor.
65 qsamplerChannel::~qsamplerChannel (void)
66 {
67 }
68
69
70 // Main application form accessor.
71 qsamplerMainForm *qsamplerChannel::mainForm(void) const
72 {
73 return m_pMainForm;
74 }
75
76
77 // The global options settings delegated property.
78 qsamplerOptions *qsamplerChannel::options (void) const
79 {
80 if (m_pMainForm == NULL)
81 return NULL;
82
83 return m_pMainForm->options();
84 }
85
86
87 // The client descriptor delegated property.
88 lscp_client_t *qsamplerChannel::client (void) const
89 {
90 if (m_pMainForm == NULL)
91 return NULL;
92
93 return m_pMainForm->client();
94 }
95
96
97 // Create a new sampler channel, if not already.
98 bool qsamplerChannel::addChannel (void)
99 {
100 if (client() == NULL)
101 return false;
102
103 // Are we a new channel?
104 if (m_iChannelID < 0) {
105 m_iChannelID = ::lscp_add_channel(client());
106 if (m_iChannelID < 0) {
107 appendMessagesClient("lscp_add_channel");
108 appendMessagesError(QObject::tr("Could not add channel.\n\nSorry."));
109 } // Otherwise it's created...
110 else appendMessages(QObject::tr("added."));
111 }
112
113 // Return whether we're a valid channel...
114 return (m_iChannelID >= 0);
115 }
116
117
118 // Remove sampler channel.
119 bool qsamplerChannel::removeChannel (void)
120 {
121 if (client() == NULL)
122 return false;
123
124 // Are we an existing channel?
125 if (m_iChannelID >= 0) {
126 if (::lscp_remove_channel(client(), m_iChannelID) != LSCP_OK) {
127 appendMessagesClient("lscp_remove_channel");
128 appendMessagesError(QObject::tr("Could not remove channel.\n\nSorry."));
129 } else {
130 // Otherwise it's removed.
131 appendMessages(QObject::tr("removed."));
132 m_iChannelID = -1;
133 }
134 }
135
136 // Return whether we've removed the channel...
137 return (m_iChannelID < 0);
138 }
139
140
141 // Channel-ID (aka Sammpler-Channel) accessors.
142 int qsamplerChannel::channelID (void) const
143 {
144 return m_iChannelID;
145 }
146
147 void qsamplerChannel::setChannelID ( int iChannelID )
148 {
149 m_iChannelID = iChannelID;
150 }
151
152
153 // Readable channel name.
154 QString qsamplerChannel::channelName (void) const
155 {
156 return (m_iChannelID < 0 ? QObject::tr("New Channel") : QObject::tr("Channel %1").arg(m_iChannelID));
157 }
158
159
160 // Engine name accessors.
161 const QString& qsamplerChannel::engineName (void) const
162 {
163 return m_sEngineName;
164 }
165
166 bool qsamplerChannel::loadEngine ( const QString& sEngineName )
167 {
168 if (client() == NULL || m_iChannelID < 0)
169 return false;
170 if (m_iInstrumentStatus == 100 && m_sEngineName == sEngineName)
171 return true;
172
173 if (::lscp_load_engine(client(), sEngineName.latin1(), m_iChannelID) != LSCP_OK) {
174 appendMessagesClient("lscp_load_engine");
175 return false;
176 }
177 appendMessages(QObject::tr("Engine: %1.").arg(sEngineName));
178
179 m_sEngineName = sEngineName;
180 return true;
181 }
182
183
184 // Instrument filename accessor.
185 const QString& qsamplerChannel::instrumentFile (void) const
186 {
187 return m_sInstrumentFile;
188 }
189
190 // Instrument index accessor.
191 int qsamplerChannel::instrumentNr (void) const
192 {
193 return m_iInstrumentNr;
194 }
195
196 // Instrument name accessor.
197 const QString& qsamplerChannel::instrumentName (void) const
198 {
199 return m_sInstrumentName;
200 }
201
202 // Instrument status accessor.
203 int qsamplerChannel::instrumentStatus (void) const
204 {
205 return m_iInstrumentStatus;
206 }
207
208 // Instrument file loader.
209 bool qsamplerChannel::loadInstrument ( const QString& sInstrumentFile, int iInstrumentNr )
210 {
211 if (client() == NULL || m_iChannelID < 0)
212 return false;
213 if (!isInstrumentFile(sInstrumentFile))
214 return false;
215 if (m_iInstrumentStatus == 100 && m_sInstrumentFile == sInstrumentFile && m_iInstrumentNr == iInstrumentNr)
216 return true;
217
218 if (::lscp_load_instrument_non_modal(client(), sInstrumentFile.latin1(), iInstrumentNr, m_iChannelID) != LSCP_OK) {
219 appendMessagesClient("lscp_load_instrument");
220 return false;
221 }
222
223 appendMessages(QObject::tr("Instrument: \"%1\" (%2).")
224 .arg(sInstrumentFile).arg(iInstrumentNr));
225
226 return setInstrument(sInstrumentFile, iInstrumentNr);
227 }
228
229
230 // Special instrument file/name/number settler.
231 bool qsamplerChannel::setInstrument ( const QString& sInstrumentFile, int iInstrumentNr )
232 {
233 m_sInstrumentFile = sInstrumentFile;
234 m_iInstrumentNr = iInstrumentNr;
235 #ifdef CONFIG_INSTRUMENT_NAME
236 m_sInstrumentName = QString::null; // We'll get it, maybe later, on channel_info...
237 #else
238 m_sInstrumentName = getInstrumentName(sInstrumentFile, iInstrumentNr, true);
239 #endif
240 m_iInstrumentStatus = 0;
241
242 return true;
243 }
244
245
246 // MIDI driver type accessors (DEPRECATED).
247 const QString& qsamplerChannel::midiDriver (void) const
248 {
249 return m_sMidiDriver;
250 }
251
252 bool qsamplerChannel::setMidiDriver ( const QString& sMidiDriver )
253 {
254 if (client() == NULL || m_iChannelID < 0)
255 return false;
256 if (m_iInstrumentStatus == 100 && m_sMidiDriver == sMidiDriver)
257 return true;
258
259 if (::lscp_set_channel_midi_type(client(), m_iChannelID, sMidiDriver.latin1()) != LSCP_OK) {
260 appendMessagesClient("lscp_set_channel_midi_type");
261 return false;
262 }
263
264 appendMessages(QObject::tr("MIDI driver: %1.").arg(sMidiDriver));
265
266 m_sMidiDriver = sMidiDriver;
267 return true;
268 }
269
270
271 // MIDI device accessors.
272 int qsamplerChannel::midiDevice (void) const
273 {
274 return m_iMidiDevice;
275 }
276
277 bool qsamplerChannel::setMidiDevice ( int iMidiDevice )
278 {
279 if (client() == NULL || m_iChannelID < 0)
280 return false;
281 if (m_iInstrumentStatus == 100 && m_iMidiDevice == iMidiDevice)
282 return true;
283
284 if (::lscp_set_channel_midi_device(client(), m_iChannelID, iMidiDevice) != LSCP_OK) {
285 appendMessagesClient("lscp_set_channel_midi_device");
286 return false;
287 }
288
289 appendMessages(QObject::tr("MIDI device: %1.").arg(iMidiDevice));
290
291 m_iMidiDevice = iMidiDevice;
292 return true;
293 }
294
295
296 // MIDI port number accessor.
297 int qsamplerChannel::midiPort (void) const
298 {
299 return m_iMidiPort;
300 }
301
302 bool qsamplerChannel::setMidiPort ( int iMidiPort )
303 {
304 if (client() == NULL || m_iChannelID < 0)
305 return false;
306 if (m_iInstrumentStatus == 100 && m_iMidiPort == iMidiPort)
307 return true;
308
309 if (::lscp_set_channel_midi_port(client(), m_iChannelID, iMidiPort) != LSCP_OK) {
310 appendMessagesClient("lscp_set_channel_midi_port");
311 return false;
312 }
313
314 appendMessages(QObject::tr("MIDI port: %1.").arg(iMidiPort));
315
316 m_iMidiPort = iMidiPort;
317 return true;
318 }
319
320
321 // MIDI channel accessor.
322 int qsamplerChannel::midiChannel (void) const
323 {
324 return m_iMidiChannel;
325 }
326
327 bool qsamplerChannel::setMidiChannel ( int iMidiChannel )
328 {
329 if (client() == NULL || m_iChannelID < 0)
330 return false;
331 if (m_iInstrumentStatus == 100 && m_iMidiChannel == iMidiChannel)
332 return true;
333
334 if (::lscp_set_channel_midi_channel(client(), m_iChannelID, iMidiChannel) != LSCP_OK) {
335 appendMessagesClient("lscp_set_channel_midi_channel");
336 return false;
337 }
338
339 appendMessages(QObject::tr("MIDI channel: %1.").arg(iMidiChannel));
340
341 m_iMidiChannel = iMidiChannel;
342 return true;
343 }
344
345
346 // Audio device accessor.
347 int qsamplerChannel::audioDevice (void) const
348 {
349 return m_iAudioDevice;
350 }
351
352 bool qsamplerChannel::setAudioDevice ( int iAudioDevice )
353 {
354 if (client() == NULL || m_iChannelID < 0)
355 return false;
356 if (m_iInstrumentStatus == 100 && m_iAudioDevice == iAudioDevice)
357 return true;
358
359 if (::lscp_set_channel_audio_device(client(), m_iChannelID, iAudioDevice) != LSCP_OK) {
360 appendMessagesClient("lscp_set_channel_audio_device");
361 return false;
362 }
363
364 appendMessages(QObject::tr("Audio device: %1.").arg(iAudioDevice));
365
366 m_iAudioDevice = iAudioDevice;
367 return true;
368 }
369
370
371 // Audio driver type accessors (DEPRECATED).
372 const QString& qsamplerChannel::audioDriver (void) const
373 {
374 return m_sAudioDriver;
375 }
376
377 bool qsamplerChannel::setAudioDriver ( const QString& sAudioDriver )
378 {
379 if (client() == NULL || m_iChannelID < 0)
380 return false;
381 if (m_iInstrumentStatus == 100 && m_sAudioDriver == sAudioDriver)
382 return true;
383
384 if (::lscp_set_channel_audio_type(client(), m_iChannelID, sAudioDriver.latin1()) != LSCP_OK) {
385 appendMessagesClient("lscp_set_channel_audio_type");
386 return false;
387 }
388
389 appendMessages(QObject::tr("Audio driver: %1.").arg(sAudioDriver));
390
391 m_sAudioDriver = sAudioDriver;
392 return true;
393 }
394
395
396 // Channel volume accessors.
397 float qsamplerChannel::volume (void) const
398 {
399 return m_fVolume;
400 }
401
402 bool qsamplerChannel::setVolume ( float fVolume )
403 {
404 if (client() == NULL || m_iChannelID < 0)
405 return false;
406 if (m_iInstrumentStatus == 100 && m_fVolume == fVolume)
407 return true;
408
409 if (::lscp_set_channel_volume(client(), m_iChannelID, fVolume) != LSCP_OK) {
410 appendMessagesClient("lscp_set_channel_volume");
411 return false;
412 }
413
414 appendMessages(QObject::tr("Volume: %1.").arg(fVolume));
415
416 m_fVolume = fVolume;
417 return true;
418 }
419
420
421 // Sampler channel mute state.
422 bool qsamplerChannel::channelMute (void) const
423 {
424 return m_bMute;
425 }
426
427 bool qsamplerChannel::setChannelMute ( bool bMute )
428 {
429 if (client() == NULL || m_iChannelID < 0)
430 return false;
431 if (m_iInstrumentStatus == 100 && ((m_bMute && bMute) || (!m_bMute && !bMute)))
432 return true;
433
434 #ifdef CONFIG_MUTE_SOLO
435 if (::lscp_set_channel_mute(client(), m_iChannelID, bMute) != LSCP_OK) {
436 appendMessagesClient("lscp_set_channel_mute");
437 return false;
438 }
439 appendMessages(QObject::tr("Mute: %1.").arg((int) bMute));
440 m_bMute = bMute;
441 return true;
442 #else
443 return false;
444 #endif
445 }
446
447
448 // Sampler channel solo state.
449 bool qsamplerChannel::channelSolo (void) const
450 {
451 return m_bSolo;
452 }
453
454 bool qsamplerChannel::setChannelSolo ( bool bSolo )
455 {
456 if (client() == NULL || m_iChannelID < 0)
457 return false;
458 if (m_iInstrumentStatus == 100 && ((m_bSolo && bSolo) || (!m_bSolo && !bSolo)))
459 return true;
460
461 #ifdef CONFIG_MUTE_SOLO
462 if (::lscp_set_channel_solo(client(), m_iChannelID, bSolo) != LSCP_OK) {
463 appendMessagesClient("lscp_set_channel_solo");
464 return false;
465 }
466 appendMessages(QObject::tr("Solo: %1.").arg((int) bSolo));
467 m_bSolo = bSolo;
468 return true;
469 #else
470 return false;
471 #endif
472 }
473
474
475 // Istrument name remapper.
476 void qsamplerChannel::updateInstrumentName (void)
477 {
478 #ifndef CONFIG_INSTRUMENT_NAME
479 m_sInstrumentName = getInstrumentName(m_sInstrumentFile,
480 m_iInstrumentNr, (options() && options()->bInstrumentNames));
481 #endif
482 }
483
484
485 // Update whole channel info state.
486 bool qsamplerChannel::updateChannelInfo (void)
487 {
488 if (client() == NULL || m_iChannelID < 0)
489 return false;
490
491 // Read channel information.
492 lscp_channel_info_t *pChannelInfo = ::lscp_get_channel_info(client(), m_iChannelID);
493 if (pChannelInfo == NULL) {
494 appendMessagesClient("lscp_get_channel_info");
495 appendMessagesError(QObject::tr("Could not get channel information.\n\nSorry."));
496 return false;
497 }
498
499 #ifdef CONFIG_INSTRUMENT_NAME
500 // We got all actual instrument datum...
501 m_sInstrumentFile = pChannelInfo->instrument_file;
502 m_iInstrumentNr = pChannelInfo->instrument_nr;
503 m_sInstrumentName = pChannelInfo->instrument_name;
504 #else
505 // First, check if intrument name has changed,
506 // taking care that instrument name lookup might be expensive,
507 // so we better make it only once and when really needed...
508 if ((m_sInstrumentFile != pChannelInfo->instrument_file) ||
509 (m_iInstrumentNr != pChannelInfo->instrument_nr)) {
510 m_sInstrumentFile = pChannelInfo->instrument_file;
511 m_iInstrumentNr = pChannelInfo->instrument_nr;
512 updateInstrumentName();
513 }
514 #endif
515 // Cache in other channel information.
516 m_sEngineName = pChannelInfo->engine_name;
517 m_iInstrumentStatus = pChannelInfo->instrument_status;
518 m_iMidiDevice = pChannelInfo->midi_device;
519 m_iMidiPort = pChannelInfo->midi_port;
520 m_iMidiChannel = pChannelInfo->midi_channel;
521 m_iAudioDevice = pChannelInfo->audio_device;
522 m_fVolume = pChannelInfo->volume;
523 #ifdef CONFIG_MUTE_SOLO
524 m_bMute = pChannelInfo->mute;
525 m_bSolo = pChannelInfo->solo;
526 #endif
527 // Some sanity checks.
528 if (m_sEngineName == "NONE" || m_sEngineName.isEmpty())
529 m_sEngineName = QString::null;
530 if (m_sInstrumentFile == "NONE" || m_sInstrumentFile.isEmpty()) {
531 m_sInstrumentFile = QString::null;
532 m_sInstrumentName = QString::null;
533 }
534
535 // Time for device info grabbing...
536 lscp_device_info_t *pDeviceInfo;
537 const QString sNone = QObject::tr("(none)");
538 // Audio device driver type.
539 pDeviceInfo = ::lscp_get_audio_device_info(client(), m_iAudioDevice);
540 if (pDeviceInfo == NULL) {
541 appendMessagesClient("lscp_get_audio_device_info");
542 m_sAudioDriver = sNone;
543 } else {
544 m_sAudioDriver = pDeviceInfo->driver;
545 }
546 // MIDI device driver type.
547 pDeviceInfo = ::lscp_get_midi_device_info(client(), m_iMidiDevice);
548 if (pDeviceInfo == NULL) {
549 appendMessagesClient("lscp_get_midi_device_info");
550 m_sMidiDriver = sNone;
551 } else {
552 m_sMidiDriver = pDeviceInfo->driver;
553 }
554
555 return true;
556 }
557
558
559 // Reset channel method.
560 bool qsamplerChannel::channelReset (void)
561 {
562 if (client() == NULL || m_iChannelID < 0)
563 return false;
564
565 if (::lscp_reset_channel(client(), m_iChannelID) != LSCP_OK) {
566 appendMessagesClient("lscp_reset_channel");
567 return false;
568 }
569
570 appendMessages(QObject::tr("reset."));
571
572 return true;
573 }
574
575
576 // Channel setup dialog form.
577 bool qsamplerChannel::channelSetup ( QWidget *pParent )
578 {
579 bool bResult = false;
580
581 appendMessages(QObject::tr("setup..."));
582
583 qsamplerChannelForm *pChannelForm = new qsamplerChannelForm(pParent);
584 if (pChannelForm) {
585 pChannelForm->setup(this);
586 bResult = pChannelForm->exec();
587 delete pChannelForm;
588 }
589
590 return bResult;
591 }
592
593
594 // Redirected messages output methods.
595 void qsamplerChannel::appendMessages( const QString& s ) const
596 {
597 if (m_pMainForm)
598 m_pMainForm->appendMessages(channelName() + ' ' + s);
599 }
600
601 void qsamplerChannel::appendMessagesColor( const QString& s,
602 const QString& c ) const
603 {
604 if (m_pMainForm)
605 m_pMainForm->appendMessagesColor(channelName() + ' ' + s, c);
606 }
607
608 void qsamplerChannel::appendMessagesText( const QString& s ) const
609 {
610 if (m_pMainForm)
611 m_pMainForm->appendMessagesText(channelName() + ' ' + s);
612 }
613
614 void qsamplerChannel::appendMessagesError( const QString& s ) const
615 {
616 if (m_pMainForm)
617 m_pMainForm->appendMessagesError(channelName() + "\n\n" + s);
618 }
619
620 void qsamplerChannel::appendMessagesClient( const QString& s ) const
621 {
622 if (m_pMainForm)
623 m_pMainForm->appendMessagesClient(channelName() + ' ' + s);
624 }
625
626
627 // Context menu event handler.
628 void qsamplerChannel::contextMenuEvent( QContextMenuEvent *pEvent )
629 {
630 if (m_pMainForm)
631 m_pMainForm->contextMenuEvent(pEvent);
632 }
633
634
635 // FIXME: Check whether a given file is an instrument file.
636 bool qsamplerChannel::isInstrumentFile ( const QString& sInstrumentFile )
637 {
638 bool bResult = false;
639
640 QFile file(sInstrumentFile);
641 if (file.open(IO_ReadOnly)) {
642 char achHeader[16];
643 if (file.readBlock(achHeader, 16)) {
644 bResult = (::memcmp(&achHeader[0], "RIFF", 4) == 0
645 && ::memcmp(&achHeader[8], "DLS LIST", 8) == 0);
646 }
647 file.close();
648 }
649
650 return bResult;
651 }
652
653
654 // Retrieve the instrument list of a instrument file (.gig).
655 QStringList qsamplerChannel::getInstrumentList( const QString& sInstrumentFile,
656 bool bInstrumentNames )
657 {
658 QString sInstrumentName = QFileInfo(sInstrumentFile).fileName();
659 QStringList instlist;
660
661 if (isInstrumentFile(sInstrumentFile)) {
662 #ifdef CONFIG_LIBGIG
663 if (bInstrumentNames) {
664 RIFF::File *pRiff = new RIFF::File(sInstrumentFile.latin1());
665 gig::File *pGig = new gig::File(pRiff);
666 gig::Instrument *pInstrument = pGig->GetFirstInstrument();
667 while (pInstrument) {
668 instlist.append((pInstrument->pInfo)->Name.c_str());
669 pInstrument = pGig->GetNextInstrument();
670 }
671 delete pGig;
672 delete pRiff;
673 }
674 else
675 #endif
676 for (int iInstrumentNr = 0; iInstrumentNr < QSAMPLER_INSTRUMENT_MAX; iInstrumentNr++)
677 instlist.append(sInstrumentName + " [" + QString::number(iInstrumentNr) + "]");
678 }
679 else instlist.append(noInstrumentName());
680
681 return instlist;
682 }
683
684
685 // Retrieve the spacific instrument name of a instrument file (.gig), given its index.
686 QString qsamplerChannel::getInstrumentName( const QString& sInstrumentFile,
687 int iInstrumentNr, bool bInstrumentNames )
688 {
689 QString sInstrumentName;
690
691 if (isInstrumentFile(sInstrumentFile)) {
692 sInstrumentName = QFileInfo(sInstrumentFile).fileName();
693 #ifdef CONFIG_LIBGIG
694 if (bInstrumentNames) {
695 RIFF::File *pRiff = new RIFF::File(sInstrumentFile.latin1());
696 gig::File *pGig = new gig::File(pRiff);
697 int iIndex = 0;
698 gig::Instrument *pInstrument = pGig->GetFirstInstrument();
699 while (pInstrument) {
700 if (iIndex == iInstrumentNr) {
701 sInstrumentName = (pInstrument->pInfo)->Name.c_str();
702 break;
703 }
704 iIndex++;
705 pInstrument = pGig->GetNextInstrument();
706 }
707 delete pGig;
708 delete pRiff;
709 }
710 else
711 #endif
712 sInstrumentName += " [" + QString::number(iInstrumentNr) + "]";
713 }
714 else sInstrumentName = noInstrumentName();
715
716 return sInstrumentName;
717 }
718
719
720 // Common invalid name-helpers.
721 QString qsamplerChannel::noEngineName (void)
722 {
723 return QObject::tr("(No engine)");
724 }
725
726 QString qsamplerChannel::noInstrumentName (void)
727 {
728 return QObject::tr("(No instrument)");
729 }
730
731 QString qsamplerChannel::loadingInstrument (void) {
732 return QObject::tr("(Loading instrument...)");
733 }
734
735
736 // end of qsamplerChannel.cpp

  ViewVC Help
Powered by ViewVC