• 0
محمد علاء الدين

إستخدام الألوان داخل console

سؤال

الكود الموجود هنا جزء من مكتبة  MathX مع بعض التعديلات
 
الألوان داخل الـ console لها إستخدامات عديدة مثل التفرقة بين الرسائل العادية و رسائل الخطأ و التحذيرات ... إلخ و مثال على هذا مترجم clang، أيضا يمكنك استخدامهم للتسلية.
 
نظام Windows
 
داخل windows توجد وسيلة واحدة لتلوين محتويات الـ console و ذلك عن طريق استخدام الأمر color بتمريره للدالة system :) ... أنا اضحك معك، هذه الوسيلة غير فعالة حيث انها تقوم بتلوين كافة الـ console و ليس اجزاء معينة و بالتالي الوسيلة الوحيدة لدينا هي بإستخدام windows API:

extern "C"{	struct CSBI	{		unsigned long long whatever;		unsigned short attributes;		unsigned long long  window;		unsigned max_window_size;	};	void* __stdcall GetStdHandle(unsigned hStd);	bool  __stdcall GetConsoleScreenBufferInfo(void* hConsoleOutput, CSBI* lpCSBI);	bool  __stdcall SetConsoleTextAttribute(void* hConsoleOutput, unsigned short wAttributes);}

إذا استخدمت windows API من قبل فستعلم بكل تأكيد ان الطريقة التى استخدمها هنا ليست الطريقة المثلي و سبب إستخدامي لها هو عدم رغبتي بإستخدام windows.h لتسريع عملية ترجمة المشروع. (ليس هذا المشروع و لكن المشروع الذى أتي منه هذا الكود).

 

دوال الـ API المستخدمه هنا متاحة بدءا من نظام تشغيل windows 2000 وحيث ان نظام التشغيل هو ويندوز فسأفترض ان مكتبة Kernel32 تم الربط عليها.

التركيب CSBI هو CONSOLE_SCREEN_BUFFER_INFO مع بعض التعديلات.
الدالة GetStdHandle استخدمها للحصول على handle للـ standard output stream و التى يتم الكتابة داخلها من داخل لغة ++c عن طريق cout.
الدالة GetConsoleScreenBufferInfo أستخدمها للحصول على الألوان الإفتراضية الخاصة بالـ console عندما يفتح أول مره حتى أستطيع من خلالهم عمل reset للألوان.
الدالة SetConsoleTextAttribute استخدمها كي اقوم بتحديد اللون الذى اريده لمخرجات الـ console.

المعامل wAttributes الموجود بالدالة SetConsoleTextAttribute هو الذى نحتاج إليه و إليك توثيق ايسر قليلا مما لدي ميكروسوفت:
البت الأول يمثل اللون الأزرق، البت الثاني يمثل اللون الأخضر، البت الثالث يمثل اللون الأحمر، البت الرابع يمثل حدة اللون فواحد يصبح اللون حاد و صفر على حالته الطبيعية.
الأربع بتات فى حالتهم الأن يغيروا من لون الخط فإن أردت منهم تغيير لون الخلفيه فقم يتحريكهم لليمين بقدر اربع بتات (بمعنى ان قيمة اول 4 بتات تماثل قيمة ثاني اربع بتات و المجموعة الأولي للون الخط و الثانية للون الخلفيه).

بتات الألوان مجتمعه تمثل 8 ألوان و هم:

+--------+-------+------+-------+------+-----+---------+--------+-------+|   id   |   0   |  1   |   2   |   3  |  4  |    5    |   6    |   7   |+--------+-------+------+-------+------+-----+---------+--------+-------+|  name  | Black | Blue | Green | Cyan | Red | Magenta | Yellow | White |+--------+-------+------+-------+------+-----+---------+--------+-------+|  name2 | أبيض  |  أصفر  | إرجواني | أحمر | سماوي | أخضر  | أزرق |  أسود |+--------+-------+------+-------+------+-----+---------+--------+-------+

 
أنظمة أخرى
 
داخل أنظمة التشغيل الأخرى أغلب الـ terminals تستخدم ANSI colors و هي عبارة عن نص يحتوى على قيمة رقمية تمثل العملية التى تريد القيام بها، هذا النص له شكل ثابت:

ESC[xxm

حيث ESC هى Escape character أى '033\' أو 'x1B\' يليها القوس ']'.
xx هي قيمة بالنظام العشرى تمثل العملية التى تريد القيام بها و هي فى حالتنا هذه ستكون إما لتغيير لون الخط أو الخلفيه او لتغيير حدة اللون.
الحرف m ليدل على انتهاء سلسلة الأمر.
 
ما يهمنا هو قيمة الألوان و هي:

+--------+-------+-----+-------+--------+------+---------+------+-------+|   id   |   0   |  1  |   2   |   3    |  4   |    5    |  6   |   7   |+--------+-------+-----+-------+--------+------+---------+------+-------+|  name  | Black | Red | Green | Yellow | Blue | Magenta | Cyan | White |+--------+-------+-----+-------+--------+------+---------+------+-------+

لقيمة لون الخط قم بإضافة 30 على قيمة اللون و لقيمة لون الخلفية قم بإضافة 40 على قيمة اللون.
لجعل اللون فاتح استخدم الرقم 1.
لإعادة اللون إلى حالته الطبيعية: توجد بعض القيم و لكن الشائع هو استخدام الرقم صفر لإعادة الإعدادات إلى حالتها الإفتراضية و هو ما استخدمه هنا.


أنواع البيانات المشتركة

فى البداية نحتاج لكتابة الكود الذى سيتم إستخدامه من خلال windows coloring و ANSI coloring.
 

// terminal colorsenum color{	black = 0,	blue = 1,	green = 2,	cyan = 3,	red = 4,	magenta = 5,	yellow = 6,	white = 7};// terminal color brightenum bright{	no  = 0, // use color standard bright	yes = 1  // use a bright color};// location to apply color intoenum place{	front = 0, // apply to foreground	back  = 1  // apply to background};// terminal color classtemplate<typename type>struct ccolor_t{	// set foreground/background colors to default state	inline static void reset() { type::reset(); }	// set color attributes	inline static void set_color(color c, bright b = no, place p = front) { type::set_color(c, b, p); }};

النوع color يمثل الألوان التى يمكننا إستخدامها.
النوع bright يمثل حدة اللون.
النوع place يمثل المكان الذى سيتم تلوينه.
النوع ccolor_t يمكنك اعتباره كجسر يقوم بإستدعاء الكود الخاص بفئة معينه هى التى تم تمريرها له من خلال المعامل type.


الكود الخاص بويندوز
 

struct windows_cc{	inline static void reset()	{		pdata& p = pdata::get();		SetConsoleTextAttribute(p.handle, p.attrib);	}	inline static void set_color(color c, bright b, place p)	{		unsigned short attrib = (unsigned short)c;		if (b == yes) attrib |= 0x8;		if (p == back) attrib <<= 4;		SetConsoleTextAttribute(pdata::get().handle, attrib);	}	struct pdata	{		void* handle;		unsigned short attrib;		inline pdata()		{			handle = GetStdHandle(unsigned(-11));			CSBI tmp;			GetConsoleScreenBufferInfo(handle, &tmp);			attrib = tmp.attributes;		}		inline static pdata& get()		{			static pdata data;			return data;		}	};};

الجزء الذى يحتاج للتوضيح هو الفئه pdata و التى استخدمها كحاوية للمتغيرات التى استخدمها و الدالة get بها تعيد إلي نسخة static منها.


الكود الخاص بـ ANSI
 

struct ansi_cc{	inline static void reset() { std::cout << "\x1B[0m"; }	inline static void set_color(color c, bright b, place p)	{		static const char* arr[] = {"\x1B[0m", "\x1B[1m"};		#define ANSI_CC(C) "\x1b[" #C "m"		static const char* const colors[][8] =		{			{ ANSI_CC(30), ANSI_CC(34), ANSI_CC(32), ANSI_CC(36), ANSI_CC(31), ANSI_CC(35), ANSI_CC(33), ANSI_CC(37) },			{ ANSI_CC(40), ANSI_CC(44), ANSI_CC(42), ANSI_CC(46), ANSI_CC(41), ANSI_CC(45), ANSI_CC(43), ANSI_CC(47) }		};				#undef ANSI_CC				std::cout << arr[b] << colors[l][c];	}};

أسئلة إضافية

ماذا نفعل لو اردنا عدم استخدام الألوان؟ فى هذه الحالة فقط قم بتعريف الفئة التالية:

struct no_colors{	inline static void reset() {}	inline static void set_color(color, bright, place) {}};

 
و لكن كيف سأحدد أى تلك الفئات استخدم؟ أيضا حتى الأن ccolor_t لا يوجد لها فائدة؟
 
لنأخذ الأمور خطوة خطوة; لعدم إستخدام الألوان نحتاج لتعريف macro لتفيد قيمته بذلك:

#define NO_COLORS  0

صفر تعنى استخدم الألوان و واحد تعني لا تستخدم الألوان، لنرى كيف سيتم إستخدام كل هذه الفئات معا:
 

#if NO_COLORS == 1// add no_colors class defination heretypedef ccolor_t<no_colors> ccolor;#elif defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__WINDOWS__)// add extern C section here// add windows_cc class defination heretypedef ccolor_t<windows_cc> ccolor;#else // use ANSI coloing// add ansi_cc class defination heretypedef ccolor_t<ansi_cc> ccolor;#endif

الأن الكود جاهز للإستخدام الأن و إليك مثال:

#include <iostream>using std::cout;int main(){	cout << "this is a default terminal color.\n";	ccolor::set_color(green, yes, front);	cout << "this is a foreground green color.\n";	ccolor::reset();	ccolor::set_color(green, yes, back);	cout << "this is a background green color.\n";	ccolor::reset();	cout << "this is a default terminal color.\n";}

المفترض ان يتم طبع النصوص لديك بالألوان.
 
 
بعض العمليات الأخرى
 
مع العلم ان ccolor يعمل بشكل جيد لكن الن يصبح الأمر أفضل إذا كتب المثال السابق كالتالي:

#include <iostream>using std::cout;int main(){	cout << "this is a default terminal color.\n";	cout << foreground(green, yes) << "this is a foreground green color.\n";	cout << reset_color << background(green, yes) << "this is a background green color.\n";	cout << reset_color << "this is a default terminal color.\n";}

فى هذا الجزء سنتطرق لكتابة iostream manipulators لجعل تنفيذ هذا المثال متاحا.

لكتابة mainpulator أولي (مثل endl) يكون كالتالي:

ostream& new_line(ostream& stm){	stm << '\n';	return stm;}

و يتم إستدعائه كالتالي:

cout << new_line;

و فى حالتنا هذه reset_color هو mainpulator أولي:

ostream& reset_color(ostream& stm){	ccolor::reset();	return stm;}

المشكله ستحدث إن اردت كتابة mainpulator له معامل أو أكثر (مثل setw, setfill) فى هذه الحالة تحتاج لكتابة فئة وسيطة لتعمل كجسر بين المعامل >>operator و الـ mainpulator الخاص بك، مثال:

struct ccolor_info{	color  c;	bright b;	place  p;	ccolor_info(color cc, bright bb, place pp) : c(cc), b(bb), p(pp) {}};ostream& operator << (ostream& stm, const ccolor_info& ci){	ccolor::set_color(ci.c, ci.b, ci.p);	return stm;}ccolor_info foreground(color c, bright b){	return ccolor_info(c, b, front);}ccolor_info background(color c, bright b){	return ccolor_info(c, b, back);}

بهذه الـ mainpulators يمكن تنفيذ المثال السابق.

 

ccolor.zip

example_1.zip

example_2.zip

 

 

و الله ولي التوفيق

تم تعديل بواسطه C++er
4

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

0 إجابة على هذا السؤال .

لاتوجد إجابات على هذا السؤال حتى الآن .

من فضلك سجل دخول لتتمكن من التعليق

ستتمكن من اضافه تعليقات بعد التسجيل



سجل دخولك الان

  • يستعرض القسم حالياً   0 members

    لا يوجد أعضاء مسجلين يشاهدون هذه الصفحة .